Package: asyncio-simple-http-server
asyncio simple HTTP ServerThis package contains a really Simple Http API Server using asyncio.It is not meant to be used in production. It is more a lightweight alternative to SimpleHTTPServer but for writing HTTP APIs.Install the packageYou can find the package athttps://pypi.org/project/asyncio-simple-http-server.Python >=3.8 is required. To install or upgrade you can use:$pipinstall--upgradeasyncio-simple-http-serverUsage ExampleTo start the server is the usual straightforward asyncio server code,
plus some registration for your HTTP API handlers.fromasyncio_simple_http_serverimportHttpServerimportasyncioasyncdefmain():# Create an instance of the HTTP Serverhttp_server=HttpServer()# Register one or more handlers# The handlers are classes containing your APIs# See below for an example and more information.http_server.add_handler(MyHandler())# If you need to enable CORS to call the APIs# from a web console that you are building (e.g. angular, react, ...)# just add the following line,# to add the required header to all the responses.http_server.add_default_response_headers({'Access-Control-Allow-Origin':'*'})# start the server and serve/wait foreverawaithttp_server.start('127.0.0.1',8888)awaithttp_server.serve_forever()if__name__=='__main__':asyncio.run(main())An handler is a simple class where some methods can be exposed as an HTTP API, using a simple annotation. It also tries to simplify things for REST by converting the request body from json to a dict and the response object to json using the json.loads() and json.dumps() methods.fromasyncio_simple_http_serverimporturi_mappingclassMyHandler:# The @uri_mapping decorator exposes this method as an HTTP API# so you can just do a HTTP GET /test-get# and you'll get back a 200 OK with a json body of {a: 10}# return values will be converted with json.dumps()@uri_mapping('/test-get')deftest_get(self):return{'a':10}# In this case the mapping says that we want a POST method# and by passing an argument named 'body' we will get the# body passed to the endpoint parsed with json.loads()# We execute "some logic" and we return the body@uri_mapping('/test-post',method='POST')deftest_post(self,body):foo=body.get('foo')print('foo:',foo)returnbodySpecial Parameter NamesTo receive the data of the HTTP request we use special parameter names, so if name a variable:request: will contains an HttpRequest object, which is the full request (method, path, header, body).headers: will contains an HttpHeaders object, which is a map of header name to values (a key can have multiple values).query_paramswill contains a dict[str, list[str]], which is a map of the query param name to list of values.raw_body: will contains the raw body as bytes.body: will contains the body converted from json using json.loads().uri_variables: will contains a dict of the variable specified in the @uri_variable_mapping and extracted from the path.Custom HTTP responses (status code, headers, ...)You can return an HttpResponse object to customize the response with your status code, headers and body encoding. You can also send a file by specifying the file path instead of the body.fromasyncio_simple_http_serverimporturi_mapping,HttpResponse,HttpHeadersclassMyHandler:# You can return an HttpResponse object to set# custom status code, headers and body@uri_mapping('/test-custom-response')deftest_custom_resp(self)->HttpResponse:headers=HttpHeaders()headers.set('X-Foo','custom stuff')returnHttpResponse(200,headers,b'test-body')# With an HttpResponse object# you can also send a file that is on disk@uri_pattern_mapping('/send-my-file')deftest_file(self)->HttpResponse:returnHttpResponse(200,file_path='/path/of/my-file')Response ExceptionsYou can also control the flow usingHttpResponseException. If not catched by your code it will result in a response with the http status code, headers and body specified.@uri_mapping('/test-exception')deftest_exception(self):headers=HttpHeaders()headers.set('X-Foo','custom stuff')raiseHttpResponseException(400,headers,b'custom-body')Routes with patternsSometimes static routes are not enough. and you want a dynamic pattern.
You can use @uri_variable_mapping and @uri_pattern_mapping to do exactly that.fromasyncio_simple_http_serverimportHttpRequest,uri_variable_mapping,uri_pattern_mappingclassMyHandler:# You can also map routes with variables# for example you can call this one with /aaa/FOO/ccc/BAR# and you'll get {'bbb': 'FOO', 'ddd', 'BAR'} as uri_variables@uri_variable_mapping('/aaa/{bbb}/ccc/{ddd}')deftest_due(self,uri_variables:dict):returnuri_variables# Or you can use regex patterns to match what you want# for example here you can call it as /any/FOO/BAR or /any/ZOO@uri_pattern_mapping('/any/(.*)')deftest_pattern(self,request:HttpRequest):returnrequest.pathOh, It uses asyncioOh yeah, I almost forgot. This server uses asyncio,
so your function can be async too. just add async to the method and use all the awaits that you want.classMyHandler:@uri_mapping('/async-sleep')asyncdeftest_async_sleep(self):awaitasyncio.sleep(4)