blob: 7676b42502bbae813227586dc0939f3164b3b2c6 [file] [log] [blame]
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -07001#!/usr/bin/env python
2#
Zsolt Harasztiaccad4a2017-01-03 21:56:48 -08003# Copyright 2017 the original author or authors.
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -07004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import os
19
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -080020import grpc
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070021from klein import Klein
22from simplejson import dumps, load
23from structlog import get_logger
24from twisted.internet import reactor, endpoints
25from twisted.internet.defer import inlineCallbacks, returnValue
26from twisted.internet.tcp import Port
27from twisted.web.server import Site
28from twisted.web.static import File
Zsolt Harasztie7b60762016-10-05 17:49:27 -070029from werkzeug.exceptions import BadRequest
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -080030from grpc import StatusCode
31
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070032
33log = get_logger()
34
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070035
36class WebServer(object):
37
38 app = Klein()
39
Matteo Scandolo54356572017-04-24 18:52:28 -070040 def __init__(self, port, work_dir, swagger_url, grpc_client):
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070041 self.port = port
42 self.site = None
43 self.work_dir = work_dir
44 self.grpc_client = grpc_client
Matteo Scandolo54356572017-04-24 18:52:28 -070045 self.swagger_url = swagger_url
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070046
47 self.swagger_ui_root_dir = os.path.abspath(
48 os.path.join(os.path.dirname(__file__), '../swagger_ui'))
49
50 self.tcp_port = None
Zsolt Harasztie7b60762016-10-05 17:49:27 -070051 self.shutting_down = False
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070052
Matteo Scandolo54356572017-04-24 18:52:28 -070053 self.add_swagger_routes(self.app, swagger_url)
54
55 def add_swagger_routes(self, app, swagger_url):
56 log.info('Publishing swagger docs at %s' % swagger_url)
57
58 @app.route(swagger_url + '/', branch=True)
59 def static(self, request):
60 try:
61 log.debug(request=request)
62 return File(self.swagger_ui_root_dir)
63 except Exception, e:
64 log.exception('file-not-found', request=request)
65
66 @app.route(swagger_url + '/v1/swagger.json')
67 def swagger_json(self, request):
68 try:
69 return File(os.path.join(self.work_dir, 'swagger.json'))
70 except Exception, e:
71 log.exception('file-not-found', request=request)
72
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070073 @inlineCallbacks
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070074 def start(self):
75 log.debug('starting')
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070076 yield self._open_endpoint()
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070077 log.info('started')
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070078 returnValue(self)
79
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070080 @inlineCallbacks
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070081 def stop(self):
82 log.debug('stopping')
83 self.shutting_down = True
84 if self.tcp_port is not None:
85 assert isinstance(self.tcp_port, Port)
86 yield self.tcp_port.socket.close()
87 log.info('stopped')
88
89 @inlineCallbacks
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070090 def _open_endpoint(self):
91 endpoint = endpoints.TCP4ServerEndpoint(reactor, self.port)
92 self.site = Site(self.app.resource())
93 self.tcp_port = yield endpoint.listen(self.site)
94 log.info('web-server-started', port=self.port)
95 self.endpoint = endpoint
96
Zsolt Harasztie7b60762016-10-05 17:49:27 -070097 def reload_generated_routes(self):
98 for fname in os.listdir(self.work_dir):
99 if fname.endswith('_gw.py'):
100 module_name = fname.replace('.py', '')
101 m = __import__(module_name)
102 assert hasattr(m, 'add_routes')
103 m.add_routes(self.app, self.grpc_client)
104 log.info('routes-loaded', module=module_name)
105
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -0800106 @app.handle_errors(grpc._channel._Rendezvous)
107 def grpc_exception(self, request, failure):
108 code = failure.value.code()
109 if code == StatusCode.NOT_FOUND:
110 request.setResponseCode(404)
111 return failure.value.details()
112 elif code == StatusCode.INVALID_ARGUMENT:
113 request.setResponseCode(400)
114 return failure.value.details()
115 elif code == StatusCode.ALREADY_EXISTS:
116 request.setResponseCode(409)
117 return failure.value.details()
Matteo Scandolo92f7f1f2017-04-28 15:56:29 -0700118 elif code == StatusCode.UNAUTHENTICATED:
119 request.setResponseCode(401)
120 return failure.value.details()
121 elif code == StatusCode.PERMISSION_DENIED:
122 request.setResponseCode(403)
123 return failure.value.details()
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -0800124 else:
125 raise
126