blob: 5cd5c7a81e58084a97f4277298527894ed7df52d [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
40 def __init__(self, port, work_dir, grpc_client):
41 self.port = port
42 self.site = None
43 self.work_dir = work_dir
44 self.grpc_client = grpc_client
45
46 self.swagger_ui_root_dir = os.path.abspath(
47 os.path.join(os.path.dirname(__file__), '../swagger_ui'))
48
49 self.tcp_port = None
Zsolt Harasztie7b60762016-10-05 17:49:27 -070050 self.shutting_down = False
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070051
52 @inlineCallbacks
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070053 def start(self):
54 log.debug('starting')
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070055 yield self._open_endpoint()
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070056 log.info('started')
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070057 returnValue(self)
58
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070059 @inlineCallbacks
Zsolt Harasztidca6fa12016-11-03 16:56:17 -070060 def stop(self):
61 log.debug('stopping')
62 self.shutting_down = True
63 if self.tcp_port is not None:
64 assert isinstance(self.tcp_port, Port)
65 yield self.tcp_port.socket.close()
66 log.info('stopped')
67
68 @inlineCallbacks
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070069 def _open_endpoint(self):
70 endpoint = endpoints.TCP4ServerEndpoint(reactor, self.port)
71 self.site = Site(self.app.resource())
72 self.tcp_port = yield endpoint.listen(self.site)
73 log.info('web-server-started', port=self.port)
74 self.endpoint = endpoint
75
Zsolt Harasztie7b60762016-10-05 17:49:27 -070076 def reload_generated_routes(self):
77 for fname in os.listdir(self.work_dir):
78 if fname.endswith('_gw.py'):
79 module_name = fname.replace('.py', '')
80 m = __import__(module_name)
81 assert hasattr(m, 'add_routes')
82 m.add_routes(self.app, self.grpc_client)
83 log.info('routes-loaded', module=module_name)
84
Zsolt Haraszti3d55ffc2016-10-03 22:26:41 -070085 # static swagger_ui website as landing page (for now)
86
87 @app.route('/', branch=True)
88 def static(self, request):
89 try:
90 log.debug(request=request)
91 return File(self.swagger_ui_root_dir)
92 except Exception, e:
93 log.exception('file-not-found', request=request)
94
95 # static swagger.json file to serve the schema
96
97 @app.route('/v1/swagger.json')
98 def swagger_json(self, request):
99 try:
100 return File(os.path.join(self.work_dir, 'swagger.json'))
101 except Exception, e:
102 log.exception('file-not-found', request=request)
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -0800103
104 @app.handle_errors(grpc._channel._Rendezvous)
105 def grpc_exception(self, request, failure):
106 code = failure.value.code()
107 if code == StatusCode.NOT_FOUND:
108 request.setResponseCode(404)
109 return failure.value.details()
110 elif code == StatusCode.INVALID_ARGUMENT:
111 request.setResponseCode(400)
112 return failure.value.details()
113 elif code == StatusCode.ALREADY_EXISTS:
114 request.setResponseCode(409)
115 return failure.value.details()
Matteo Scandolo92f7f1f2017-04-28 15:56:29 -0700116 elif code == StatusCode.UNAUTHENTICATED:
117 request.setResponseCode(401)
118 return failure.value.details()
119 elif code == StatusCode.PERMISSION_DENIED:
120 request.setResponseCode(403)
121 return failure.value.details()
Zsolt Harasztic8cfdf32016-11-28 14:28:39 -0800122 else:
123 raise
124