refactored api servers, especially the public server. Client server to recieve similar refactoring soon

master
Kevin Froman 2019-07-02 01:32:26 -05:00
parent 54dabefe45
commit 10ef9ce41b
11 changed files with 286 additions and 213 deletions

View File

@ -17,61 +17,20 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
import random, threading, hmac, base64, time, os, json, socket import threading, hmac, base64, time, os
from gevent.pywsgi import WSGIServer, WSGIHandler from gevent.pywsgi import WSGIServer
from gevent import Timeout
import flask import flask
from flask import request, Response, abort, send_from_directory from flask import request, Response, abort, send_from_directory
import core import core
import onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionrblockapi import onionrexceptions, onionrcrypto, logger, config
import httpapi import httpapi
from httpapi import friendsapi, profilesapi, configapi, miscpublicapi, miscclientapi, insertblock, onionrsitesapi from httpapi import friendsapi, profilesapi, configapi, miscpublicapi, miscclientapi, insertblock, onionrsitesapi
from onionrservices import httpheaders from onionrservices import httpheaders
import onionr import onionr
from onionrutils import bytesconverter, stringvalidators, epoch, mnemonickeys from onionrutils import bytesconverter, stringvalidators, epoch, mnemonickeys
from httpapi import apiutils from httpapi import apiutils, security, fdsafehandler
config.reload() config.reload()
class FDSafeHandler(WSGIHandler):
'''Our WSGI handler. Doesn't do much non-default except timeouts'''
def handle(self):
self.timeout = Timeout(120, Exception)
self.timeout.start()
try:
WSGIHandler.handle(self)
except Exception:
self.handle_error()
finally:
self.timeout.close()
def handle_error(self):
if v is self.timeout:
self.result = [b"Timeout"]
self.start_response("200 OK", [])
self.process_result()
else:
WSGIHandler.handle_error(self)
def setBindIP(filePath=''):
'''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
if config.get('general.random_bind_ip', True):
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
data = '.'.join(hostOctets)
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((data, 0))
except OSError:
# if mac/non-bindable, show warning and default to 127.0.0.1
logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
data = '127.0.0.1'
s.close()
else:
data = '127.0.0.1'
if filePath != '':
with open(filePath, 'w') as bindFile:
bindFile.write(data)
return data
class PublicAPI: class PublicAPI:
''' '''
@ -82,94 +41,27 @@ class PublicAPI:
app = flask.Flask('PublicAPI') app = flask.Flask('PublicAPI')
self.i2pEnabled = config.get('i2p.host', False) self.i2pEnabled = config.get('i2p.host', False)
self.hideBlocks = [] # Blocks to be denied sharing self.hideBlocks = [] # Blocks to be denied sharing
self.host = setBindIP(clientAPI._core.publicApiHostFile) self.host = apiutils.setbindip.set_bind_IP(clientAPI._core.publicApiHostFile, clientAPI._core)
self.torAdder = clientAPI._core.hsAddress self.torAdder = clientAPI._core.hsAddress
self.i2pAdder = clientAPI._core.i2pAddress self.i2pAdder = clientAPI._core.i2pAddress
self.bindPort = config.get('client.public.port') self.bindPort = config.get('client.public.port')
self.lastRequest = 0 self.lastRequest = 0
self.hitCount = 0 # total rec requests to public api since server started self.hitCount = 0 # total rec requests to public api since server started
self.config = config
self.clientAPI = clientAPI
self.API_VERSION = onionr.API_VERSION
logger.info('Running public api on %s:%s' % (self.host, self.bindPort)) logger.info('Running public api on %s:%s' % (self.host, self.bindPort))
@app.before_request
def validateRequest():
'''Validate request has the correct hostname'''
# If high security level, deny requests to public (HS should be disabled anyway for Tor, but might not be for I2P)
if config.get('general.security_level', default=1) > 0:
abort(403)
if type(self.torAdder) is None and type(self.i2pAdder) is None:
# abort if our hs addresses are not known
abort(403)
if request.host not in (self.i2pAdder, self.torAdder):
# Disallow connection if wrong HTTP hostname, in order to prevent DNS rebinding attacks
abort(403)
self.hitCount += 1 # raise hit count for valid requests
@app.after_request
def sendHeaders(resp):
'''Send api, access control headers'''
resp = httpheaders.set_default_onionr_http_headers(resp)
# Network API version
resp.headers['X-API'] = onionr.API_VERSION
self.lastRequest = epoch.get_rounded_epoch(roundS=5)
return resp
@app.route('/')
def banner():
# Display a bit of information to people who visit a node address in their browser
try:
with open('static-data/index.html', 'r') as html:
resp = Response(html.read(), mimetype='text/html')
except FileNotFoundError:
resp = Response("")
return resp
@app.route('/getblocklist')
def getBlockList():
return httpapi.miscpublicapi.public_block_list(clientAPI, self, request)
@app.route('/getdata/<name>')
def getBlockData(name):
# Share data for a block if we have it
return httpapi.miscpublicapi.public_get_block_data(clientAPI, self, name)
@app.route('/www/<path:path>')
def wwwPublic(path):
# A way to share files directly over your .onion
if not config.get("www.public.run", True):
abort(403)
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
@app.route('/ping')
def ping():
# Endpoint to test if nodes are up
return Response("pong!")
@app.route('/pex')
def peerExchange():
response = ','.join(clientAPI._core.listAdders(recent=3600))
if len(response) == 0:
response = ''
return Response(response)
@app.route('/announce', methods=['post'])
def acceptAnnounce():
resp = httpapi.miscpublicapi.announce(clientAPI, request)
return resp
@app.route('/upload', methods=['post'])
def upload():
'''Accept file uploads. In the future this will be done more often than on creation
to speed up block sync
'''
return httpapi.miscpublicapi.upload(clientAPI, request)
# Set instances, then startup our public api server # Set instances, then startup our public api server
clientAPI.setPublicAPIInstance(self) clientAPI.setPublicAPIInstance(self)
while self.torAdder == '': while self.torAdder == '':
clientAPI._core.refreshFirstStartVars() clientAPI._core.refreshFirstStartVars()
self.torAdder = clientAPI._core.hsAddress self.torAdder = clientAPI._core.hsAddress
time.sleep(0.1) time.sleep(0.1)
self.httpServer = WSGIServer((self.host, self.bindPort), app, log=None, handler_class=FDSafeHandler)
app.register_blueprint(security.public.PublicAPISecurity(self).public_api_security_bp)
app.register_blueprint(miscpublicapi.endpoints.PublicEndpoints(self).public_endpoints_bp)
self.httpServer = WSGIServer((self.host, self.bindPort), app, log=None, handler_class=fdsafehandler.FDSafeHandler)
self.httpServer.serve_forever() self.httpServer.serve_forever()
class API: class API:
@ -200,12 +92,13 @@ class API:
self.publicAPI = None # gets set when the thread calls our setter... bad hack but kinda necessary with flask self.publicAPI = None # gets set when the thread calls our setter... bad hack but kinda necessary with flask
#threading.Thread(target=PublicAPI, args=(self,)).start() #threading.Thread(target=PublicAPI, args=(self,)).start()
self.host = setBindIP(self._core.privateApiHostFile) self.host = apiutils.setbindip.set_bind_IP(self._core.privateApiHostFile, self._core)
logger.info('Running api on %s:%s' % (self.host, self.bindPort)) logger.info('Running api on %s:%s' % (self.host, self.bindPort))
self.httpServer = '' self.httpServer = ''
self.queueResponse = {} self.queueResponse = {}
onionrInst.setClientAPIInst(self) onionrInst.setClientAPIInst(self)
app.register_blueprint(security.client.ClientAPISecurity(self).client_api_security_bp)
app.register_blueprint(friendsapi.friends) app.register_blueprint(friendsapi.friends)
app.register_blueprint(profilesapi.profile_BP) app.register_blueprint(profilesapi.profile_BP)
app.register_blueprint(configapi.config_BP) app.register_blueprint(configapi.config_BP)
@ -303,7 +196,7 @@ class API:
def getHumanReadable(name): def getHumanReadable(name):
return Response(mnemonickeys.get_human_readable_ID(name)) return Response(mnemonickeys.get_human_readable_ID(name))
self.httpServer = WSGIServer((self.host, bindPort), app, log=None, handler_class=FDSafeHandler) self.httpServer = WSGIServer((self.host, bindPort), app, log=None, handler_class=fdsafehandler.FDSafeHandler)
self.httpServer.serve_forever() self.httpServer.serve_forever()
def setPublicAPIInstance(self, inst): def setPublicAPIInstance(self, inst):

View File

@ -21,7 +21,7 @@ import logger
from onionrutils import stringvalidators from onionrutils import stringvalidators
def lookup_new_peer_transports_with_communicator(comm_inst): def lookup_new_peer_transports_with_communicator(comm_inst):
logger.info('Looking up new addresses...', terminal=True) logger.info('Looking up new addresses...')
tryAmount = 1 tryAmount = 1
newPeers = [] newPeers = []
for i in range(tryAmount): for i in range(tryAmount):

View File

@ -1,44 +1,3 @@
import json from . import shutdown, setbindip, getblockdata
import core, onionrblockapi
from onionrutils import bytesconverter, stringvalidators
from . import shutdown
class GetBlockData: GetBlockData = getblockdata.GetBlockData
def __init__(self, client_api_inst=None):
if client_api_inst is None:
self.client_api_inst = None
self.c = core.Core()
elif isinstance(client_api_inst, core.Core):
self.client_api_inst = None
self.c = client_api_inst
else:
self.client_api_Inst = client_api_inst
self.c = core.Core()
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False):
assert stringvalidators.validate_hash(bHash)
bl = onionrblockapi.Block(bHash, core=self.c)
if decrypt:
bl.decrypt()
if bl.isEncrypted and not bl.decrypted:
raise ValueError
if not raw:
if not headerOnly:
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
for x in list(retData.keys()):
try:
retData[x] = retData[x].decode()
except AttributeError:
pass
else:
validSig = False
signer = bytesconverter.bytes_to_str(bl.signer)
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
validSig = True
bl.bheader['validSig'] = validSig
bl.bheader['meta'] = ''
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
return json.dumps(retData)
else:
return bl.raw

View File

@ -0,0 +1,42 @@
import json
import core, onionrblockapi
from onionrutils import bytesconverter, stringvalidators
class GetBlockData:
def __init__(self, client_api_inst=None):
if client_api_inst is None:
self.client_api_inst = None
self.c = core.Core()
elif isinstance(client_api_inst, core.Core):
self.client_api_inst = None
self.c = client_api_inst
else:
self.client_api_Inst = client_api_inst
self.c = core.Core()
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False):
assert stringvalidators.validate_hash(bHash)
bl = onionrblockapi.Block(bHash, core=self.c)
if decrypt:
bl.decrypt()
if bl.isEncrypted and not bl.decrypted:
raise ValueError
if not raw:
if not headerOnly:
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
for x in list(retData.keys()):
try:
retData[x] = retData[x].decode()
except AttributeError:
pass
else:
validSig = False
signer = bytesconverter.bytes_to_str(bl.signer)
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
validSig = True
bl.bheader['validSig'] = validSig
bl.bheader['meta'] = ''
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
return json.dumps(retData)
else:
return bl.raw

View File

@ -0,0 +1,25 @@
import random, socket
import config, logger
def set_bind_IP(filePath='', core_inst=None):
'''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
if not core_inst is None:
config = core_inst.config
if config.get('general.random_bind_ip', True):
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
data = '.'.join(hostOctets)
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((data, 0))
except OSError:
# if mac/non-bindable, show warning and default to 127.0.0.1
logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
data = '127.0.0.1'
s.close()
else:
data = '127.0.0.1'
if filePath != '':
with open(filePath, 'w') as bindFile:
bindFile.write(data)
return data

View File

@ -0,0 +1,21 @@
from gevent.pywsgi import WSGIServer, WSGIHandler
from gevent import Timeout
class FDSafeHandler(WSGIHandler):
'''Our WSGI handler. Doesn't do much non-default except timeouts'''
def handle(self):
self.timeout = Timeout(120, Exception)
self.timeout.start()
try:
WSGIHandler.handle(self)
except Exception:
self.handle_error()
finally:
self.timeout.close()
def handle_error(self):
if v is self.timeout:
self.result = [b"Timeout"]
self.start_response("200 OK", [])
self.process_result()
else:
WSGIHandler.handle_error(self)

View File

@ -1,23 +1,4 @@
''' from . import announce, upload, getblocks, endpoints
Onionr - Private P2P Communication
Public endpoints to do various block sync actions and announcement
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
from . import announce, upload, getblocks
announce = announce.handle_announce # endpoint handler for accepting peer announcements announce = announce.handle_announce # endpoint handler for accepting peer announcements
upload = upload.accept_upload # endpoint handler for accepting public uploads upload = upload.accept_upload # endpoint handler for accepting public uploads

View File

@ -0,0 +1,77 @@
'''
Onionr - Private P2P Communication
Misc public API endpoints too small to need their own file and that need access to the public api inst
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
from flask import Response, Blueprint, request, send_from_directory, abort
from . import getblocks, upload, announce
class PublicEndpoints:
def __init__(self, public_api):
client_API = public_api.clientAPI
config = client_API._core.config
public_endpoints_bp = Blueprint('publicendpoints', __name__)
self.public_endpoints_bp = public_endpoints_bp
@public_endpoints_bp.route('/')
def banner():
# Display a bit of information to people who visit a node address in their browser
try:
with open('static-data/index.html', 'r') as html:
resp = Response(html.read(), mimetype='text/html')
except FileNotFoundError:
resp = Response("")
return resp
@public_endpoints_bp.route('/getblocklist')
def get_block_list():
return getblocks.public_block_list(client_API, public_api, request)
@public_endpoints_bp.route('/getdata/<name>')
def get_block_data(name):
# Share data for a block if we have it
return getblocks.public_get_block_data(client_API, public_api, name)
@public_endpoints_bp.route('/www/<path:path>')
def www_public(path):
# A way to share files directly over your .onion
if not config.get("www.public.run", True):
abort(403)
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
@public_endpoints_bp.route('/ping')
def ping():
# Endpoint to test if nodes are up
return Response("pong!")
@public_endpoints_bp.route('/pex')
def peer_exchange():
response = ','.join(client_API._core.listAdders(recent=3600))
if len(response) == 0:
response = ''
return Response(response)
@public_endpoints_bp.route('/announce', methods=['post'])
def accept_announce():
resp = httpapi.miscpublicapi.announce(client_API, request)
return resp
@public_endpoints_bp.route('/upload', methods=['post'])
def upload():
'''Accept file uploads. In the future this will be done more often than on creation
to speed up block sync
'''
return upload.accept_upload(client_API, request)

View File

@ -1,6 +1,25 @@
'''
Onionr - Private P2P Communication
Process incoming requests to the client api server to validate they are legitimate
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import hmac import hmac
from flask import Blueprint, request, abort from flask import Blueprint, request, abort
from onionrservices import httpheaders
# Be extremely mindful of this. These are endpoints available without a password # Be extremely mindful of this. These are endpoints available without a password
whitelist_endpoints = ('siteapi.site', 'www', 'staticfiles.onionrhome', 'staticfiles.homedata', whitelist_endpoints = ('siteapi.site', 'www', 'staticfiles.onionrhome', 'staticfiles.homedata',
'staticfiles.board', 'staticfiles.profiles', 'staticfiles.board', 'staticfiles.profiles',
@ -9,28 +28,34 @@ whitelist_endpoints = ('siteapi.site', 'www', 'staticfiles.onionrhome', 'staticf
'staticfiles.mail', 'staticfiles.mailindex', 'staticfiles.friends', 'staticfiles.friendsindex', 'staticfiles.mail', 'staticfiles.mailindex', 'staticfiles.friends', 'staticfiles.friendsindex',
'staticfiles.clandestine', 'staticfiles.clandestineIndex') 'staticfiles.clandestine', 'staticfiles.clandestineIndex')
@app.before_request class ClientAPISecurity:
def validateRequest(): def __init__(self, client_api):
'''Validate request has set password and is the correct hostname''' client_api_security_bp = Blueprint('clientapisecurity', __name__)
# For the purpose of preventing DNS rebinding attacks self.client_api_security_bp = client_api_security_bp
if request.host != '%s:%s' % (self.host, self.bindPort): self.client_api = client_api
abort(403)
if request.endpoint in whitelist_endpoints:
return
try:
if not hmac.compare_digest(request.headers['token'], self.clientToken):
if not hmac.compare_digest(request.form['token'], self.clientToken):
abort(403)
except KeyError:
if not hmac.compare_digest(request.form['token'], self.clientToken):
abort(403)
@app.after_request @client_api_security_bp.before_app_request
def afterReq(resp): def validate_request():
# Security headers '''Validate request has set password and is the correct hostname'''
resp = httpheaders.set_default_onionr_http_headers(resp) # For the purpose of preventing DNS rebinding attacks
if request.endpoint == 'site': if request.host != '%s:%s' % (client_api.host, client_api.bindPort):
resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:" abort(403)
else: if request.endpoint in whitelist_endpoints:
resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self'; img-src 'self'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'self'" return
return resp try:
if not hmac.compare_digest(request.headers['token'], client_api.clientToken):
if not hmac.compare_digest(request.form['token'], client_api.clientToken):
abort(403)
except KeyError:
if not hmac.compare_digest(request.form['token'], client_api.clientToken):
abort(403)
@client_api_security_bp.after_app_request
def after_req(resp):
# Security headers
resp = httpheaders.set_default_onionr_http_headers(resp)
if request.endpoint == 'site':
resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
else:
resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self'; img-src 'self'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'self'"
return resp

View File

@ -0,0 +1,50 @@
'''
Onionr - Private P2P Communication
Process incoming requests to the public api server for certain attacks
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
from flask import Blueprint, request, abort
from onionrservices import httpheaders
from onionrutils import epoch
class PublicAPISecurity:
def __init__(self, public_api):
public_api_security_bp = Blueprint('publicapisecurity', __name__)
self.public_api_security_bp = public_api_security_bp
@public_api_security_bp.before_app_request
def validate_request():
'''Validate request has the correct hostname'''
# If high security level, deny requests to public (HS should be disabled anyway for Tor, but might not be for I2P)
if public_api.config.get('general.security_level', default=1) > 0:
abort(403)
if type(public_api.torAdder) is None and type(public_api.i2pAdder) is None:
# abort if our hs addresses are not known
abort(403)
if request.host not in (public_api.i2pAdder, public_api.torAdder):
# Disallow connection if wrong HTTP hostname, in order to prevent DNS rebinding attacks
abort(403)
public_api.hitCount += 1 # raise hit count for valid requests
@public_api_security_bp.after_app_request
def send_headers(resp):
'''Send api, access control headers'''
resp = httpheaders.set_default_onionr_http_headers(resp)
# Network API version
resp.headers['X-API'] = public_api.API_VERSION
public_api.lastRequest = epoch.get_rounded_epoch(roundS=5)
return resp

View File

@ -23,7 +23,7 @@ from flask import Flask
import core, logger, httpapi import core, logger, httpapi
import onionrexceptions import onionrexceptions
from netcontroller import getOpenPort from netcontroller import getOpenPort
import api from httpapi import apiutils
from onionrutils import stringvalidators, basicrequests, bytesconverter from onionrutils import stringvalidators, basicrequests, bytesconverter
from . import httpheaders from . import httpheaders
@ -40,7 +40,7 @@ class ConnectionServer:
socks = core_inst.config.get('tor.socksport') # Load config for Tor socks port for proxy socks = core_inst.config.get('tor.socksport') # Load config for Tor socks port for proxy
service_app = Flask(__name__) # Setup Flask app for server. service_app = Flask(__name__) # Setup Flask app for server.
service_port = getOpenPort() service_port = getOpenPort()
service_ip = api.setBindIP() service_ip = apiutils.setbindip.set_bind_IP(core_inst=self.core_inst)
http_server = WSGIServer(('127.0.0.1', service_port), service_app, log=None) http_server = WSGIServer(('127.0.0.1', service_port), service_app, log=None)
core_inst.onionrInst.communicatorInst.service_greenlets.append(http_server) core_inst.onionrInst.communicatorInst.service_greenlets.append(http_server)