Repository: shazow/gohttplib Branch: master Commit: 8987fd2fc7e0 Files: 16 Total size: 14.3 KB Directory structure: gitextract_ob15tj05/ ├── LICENSE ├── Makefile ├── README.md ├── examples/ │ ├── c/ │ │ └── main.c │ └── python/ │ ├── MANIFEST.in │ ├── Makefile │ ├── README.rst │ ├── gohttp/ │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── _gohttplib.py │ │ └── build_gohttplib.py │ └── setup.py ├── gohttplib.c ├── gohttplib.go ├── ptrproxy.go └── responsewriter.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Andrey Petrov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ OS = $(shell uname -s) ifeq ($(OS), Darwin) LDFLAGS += -framework CoreFoundation -framework Security endif all: examples examples: example-c example-python example-c: build/gohttp-c example-c-static: build/gohttp-c-static example-python: examples/python/gohttp/_gohttplib.py build/: mkdir build build/libgohttp.a: *.go go build -buildmode=c-archive -o $@ build/libgohttp.so: *.go build go build -buildmode=c-shared -o libgohttp.so && mv libgohttp.* $(dir $@) build/gohttp-c-static: examples/c/ build/libgohttp.a gcc -o $@ examples/c/main.c build/libgohttp.a $(LDFLAGS) -lpthread build/gohttp-c: examples/c/ build/libgohttp.so gcc -o $@ examples/c/main.c -Lbuild -lgohttp -lpthread $(LDFLAGS) examples/python/gohttp/libgohttp.so: build/libgohttp.so cp $< $@ examples/python/gohttp/_gohttplib.py: examples/python/gohttp/build_gohttplib.py examples/python/gohttp/libgohttp.so cd examples/python && python gohttp/build_gohttplib.py clean: rm -rf build/ benchmark: ab -n 10000 -c 10 -s 3 http://127.0.0.1:$(PORT)/ ================================================ FILE: README.md ================================================ # gohttplib Shared library that exposes Go's `net/http.Server` with externally-bindable handlers. This is a silly project for experimenting with Go buildmodes. I gave a [talk about this at PyCon 2016](https://www.youtube.com/watch?v=CkDwb5koRTc). **Status**: Tiny subset of the `http.HandlerFunc` callback gets passed to a C handler callback. Python bindings are working, too. ## Getting Started Requirements: - Go 1.5 or newer. - C example: make, gcc - Python example: cffi, python-cffi ``` $ git clone https://github.com/shazow/gohttplib/ $ cd gohttplib $ make examples ``` ### Example: C C example can be linked against a shared object (`libgohttp.so` generated by `go build -buildmode=c-shared`) or against a static library archive (`libgohttp.a` generated by `go build -buildmode=c-archive`). By default, we link against the shared object because that's what the Python example uses too. Linked against our shared object: ``` $ make example-c $ DYLD_LIBRARY_PATH=build/ ./build/gohttp-c ``` Note that you'll need to make sure that `libgohttp.so` is findable at runtime. Now you can request `http://localhost:8000/hello` and the C handler in `examples/c/main.c` will handle it! Linked against our static library archive: ``` $ make example-c-static $ ./build/gohttp-c-static ``` The static archive gets built into the binary so it's more portable during runtime. Note the size differences: ``` 8.8K gohttp-c 5.1M gohttp-c-static 7.5M libgohttp.a 6.9M libgohttp.so ``` ### Example: Python The Python example uses [python-cffi](https://cffi.readthedocs.org/en/latest/) (you'll need python-cffi installed) to link against the shared object. ``` $ make example-python $ cd examples/python $ python -m gohttp * Running on http://127.0.0.1:5000/ ``` Or write your own handler using this library: ```python from gohttp import route, run @route('/') def index(w, req): w.write("%s %s %s\n" % (req.method, req.host, req.url)) w.write("Hello, world.\n") run(host='127.0.0.1', port=5000) ``` ## References & Credit * [Go Execution Modes](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/view#) document. * Every single StackOverflow thread tagged [cgo]. * [github.com/jrick/buildmodes](https://github.com/jrick/buildmodes) * [github.com/wolever/python-cffi-example](https://github.com/wolever/python-cffi-example/) * Invaluable help with python-cffi from [@wolever](https://twitter.com/wolever), [@paultag](https://twitter.com/paultag), and [@dstufft](https://twitter.com/dstufft). ## Sponsors This project was made possible thanks to [Glider Labs](http://gliderlabs.com/). ## License MIT ================================================ FILE: examples/c/main.c ================================================ #include #include "../../build/libgohttp.h" void handler(ResponseWriterPtr w, Request *req) { printf("handler: %s %s\n%s\n\n%s\n", req->Method, req->URL, req->Headers, req->Body); char *buf = "Hello, world"; int n = ResponseWriter_Write(w, buf, 12); if (n == EOF) { printf("handler: Failed to write.\n"); ResponseWriter_WriteHeader(w, 500); return; } printf("handler: Wrote %d bytes.\n", n); } int main() { HandleFunc("/", &handler); printf("Listening on :8000\n"); ListenAndServe(":8000"); return 0; } ================================================ FILE: examples/python/MANIFEST.in ================================================ include README.rst include gohttp/*.so ================================================ FILE: examples/python/Makefile ================================================ dist/: build build: python setup.py sdist bdist publish: dist/ twine upload dist/* clean: rm -rf build/ dist/ ================================================ FILE: examples/python/README.rst ================================================ gohttp: Bindings for gohttplib ============================== *Warning*: This library currently ships with a shared object of gohttplib compiled for OSX. It will not work on other platforms at the moment. See ``_ for details. Usage ----- :: from gohttp import route, run @route('/') def index(w, req): w.write("%s %s %s\n" % (req.method, req.host, req.url)) w.write("Hello, world.\n") run(host='127.0.0.1', port=5000) License ------- MIT. ================================================ FILE: examples/python/gohttp/__init__.py ================================================ import os from ._gohttplib import ffi lib = ffi.dlopen(os.path.join(os.path.dirname(__file__), "libgohttp.so")) # Hang onto handlers so they don't get gc'd _handlers = [] class ResponseWriter: def __init__(self, w): self._w = w def write(self, body): n = lib.ResponseWriter_Write(self._w, body, len(body)) if n != len(body): raise IOError("Failed to write to ResponseWriter.") def set_status(self, code): lib.ResponseWriter_WriteHeader(self._w, code) class Request: def __init__(self, req): self._req = req @property def method(self): return ffi.string(self._req.Method) @property def host(self): return ffi.string(self._req.Host) @property def url(self): return ffi.string(self._req.URL) @property def body(self): return ffi.string(self._req.Body) @property def headers(self): return ffi.string(self._req.Headers) def __repr__(self): return "{self.method} {self.url}".format(self=self) def route(pattern, fn=None): """ Can be used as a decorator. :param pattern: Address pattern to match against. :param fn: Handler to call when pattern is matched. Handler is given a ResponseWriter and Request object. """ def wrapped(fn): @ffi.callback("void(ResponseWriterPtr, Request*)") def handler(w, req): fn(ResponseWriter(w), Request(req)) lib.HandleFunc(str.encode(pattern), handler) _handlers.append(handler) if fn: return wrapped(fn) return wrapped def run(host='', port=5000): bind = '{}:{}'.format(host or '', port) print(" * Running on http://{}/".format(bind)) lib.ListenAndServe(str.encode(bind)) def shutdown(): lib.Shutdown() ================================================ FILE: examples/python/gohttp/__main__.py ================================================ from . import route, run if __name__ == '__main__': @route('/') def index(w, req): w.write(b"%s %s %s\n%s\n\n%s\n\n" % (req.method, req.host, req.url, req.headers, req.body)) w.write(b"Hello, world.\n") run() ================================================ FILE: examples/python/gohttp/_gohttplib.py ================================================ # auto-generated file import _cffi_backend ffi = _cffi_backend.FFI('gohttp._gohttplib', _version = 0x2601, _types = b'\x00\x00\x03\x0D\x00\x00\x08\x01\x00\x00\x1D\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x02\x11\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x02\x11\x00\x00\x0C\x03\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x08\x01\x00\x00\x1B\x03\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x08\x01\x00\x00\x0E\x11\x00\x00\x0A\x11\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x08\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x1E\x0D\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x1D\x03\x00\x00\x02\x01\x00\x00\x00\x01', _globals = (b'\x00\x00\x10\x23Call_HandleFunc',0,b'\x00\x00\x08\x23HandleFunc',0,b'\x00\x00\x05\x23ListenAndServe',0,b'\x00\x00\x00\x23ResponseWriter_Write',0,b'\x00\x00\x15\x23ResponseWriter_WriteHeader',0,b'\x00\x00\x19\x23Shutdown',0), _struct_unions = ((b'\x00\x00\x00\x1B\x00\x00\x00\x02Request_',b'\x00\x00\x1C\x11Method',b'\x00\x00\x1C\x11Host',b'\x00\x00\x1C\x11URL',b'\x00\x00\x1C\x11Body',b'\x00\x00\x1C\x11Headers'),), _typenames = (b'\x00\x00\x00\x0CFuncPtr',b'\x00\x00\x00\x1BRequest',b'\x00\x00\x00\x01ResponseWriterPtr'), ) ================================================ FILE: examples/python/gohttp/build_gohttplib.py ================================================ from cffi import FFI ffi = FFI() ffi.set_source("gohttp._gohttplib", None) # Copied from the Go-generated gohttplib.h ffi.cdef(""" typedef struct Request_ { const char *Method; const char *Host; const char *URL; const char *Body; const char *Headers; } Request; typedef unsigned int ResponseWriterPtr; typedef void FuncPtr(ResponseWriterPtr w, Request *r); void Call_HandleFunc(ResponseWriterPtr w, Request *r, FuncPtr *fn); void ListenAndServe(char* p0); void Shutdown(); void HandleFunc(char* p0, FuncPtr* p1); int ResponseWriter_Write(unsigned int p0, char* p1, int p2); void ResponseWriter_WriteHeader(unsigned int p0, int p1); """) if __name__ == "__main__": ffi.compile() ================================================ FILE: examples/python/setup.py ================================================ #!/usr/bin/env python import os import sys from setuptools import setup, find_packages os.chdir(os.path.dirname(sys.argv[0]) or ".") setup( name="gohttp", version="0.3.3", description="Bindings for gohttplib, exposing Go's http.Server", long_description=open("README.rst", "rt").read(), url="https://github.com/shazow/gohttplib", author="Andrey Petrov", author_email="andrey.petrov@shazow.net", classifiers=[ "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", ], packages=find_packages(), install_requires=["cffi>=1.0.0"], setup_requires=["cffi>=1.0.0"], cffi_modules=[ "./gohttp/build_gohttplib.py:ffi", ], package_data={ "gohttp": ["libgohttp.so"], }, ) ================================================ FILE: gohttplib.c ================================================ #include "_cgo_export.h" void Call_HandleFunc(ResponseWriterPtr w, Request *req, FuncPtr *fn) { return fn(w, req); } ================================================ FILE: gohttplib.go ================================================ package main /* #include typedef struct Request_ { const char *Method; const char *Host; const char *URL; const char *Body; const char *Headers; } Request; typedef unsigned int ResponseWriterPtr; typedef void FuncPtr(ResponseWriterPtr w, Request *r); extern void Call_HandleFunc(ResponseWriterPtr w, Request *r, FuncPtr *fn); */ import "C" import ( "bytes" "net/http" "unsafe" "context" ) var cpointers = PtrProxy() var srv http.Server = http.Server{} //export ListenAndServe func ListenAndServe(caddr *C.char) { addr := C.GoString(caddr) srv.Addr = addr srv.ListenAndServe() } //export Shutdown func Shutdown() { srv.Shutdown(context.Background()) } //export HandleFunc func HandleFunc(cpattern *C.char, cfn *C.FuncPtr) { // C-friendly wrapping for our http.HandleFunc call. pattern := C.GoString(cpattern) http.HandleFunc(pattern, func(w http.ResponseWriter, req *http.Request) { // Convert the headers to a String headerBuffer := new(bytes.Buffer) req.Header.Write(headerBuffer) headersString := headerBuffer.String() // Convert the request body to a String bodyBuffer := new(bytes.Buffer) bodyBuffer.ReadFrom(req.Body) bodyString := bodyBuffer.String() // Wrap relevant request fields in a C-friendly datastructure. creq := C.Request{ Method: C.CString(req.Method), Host: C.CString(req.Host), URL: C.CString(req.URL.String()), Body: C.CString(bodyString), Headers: C.CString(headersString), } // Convert the ResponseWriter interface instance to an opaque C integer // that we can safely pass along. wPtr := cpointers.Ref(unsafe.Pointer(&w)) // Call our C function pointer using our C shim. C.Call_HandleFunc(C.ResponseWriterPtr(wPtr), &creq, cfn) // release the C memory C.free(unsafe.Pointer(creq.Method)) C.free(unsafe.Pointer(creq.Host)) C.free(unsafe.Pointer(creq.URL)) C.free(unsafe.Pointer(creq.Body)) C.free(unsafe.Pointer(creq.Headers)) // Release the ResponseWriter from the registry since we're done with // this response. cpointers.Free(wPtr) }) } func main() {} ================================================ FILE: ptrproxy.go ================================================ package main import ( "C" "sync" "unsafe" ) // PtrProxy creates a safe pointer registry. It hangs on to an unsafe.Pointer and // returns a totally-safe C.uint ID that can be used to look up the original // pointer by using it. func PtrProxy() *ptrProxy { return &ptrProxy{ lookup: map[uint]unsafe.Pointer{}, } } type ptrProxy struct { sync.Mutex count uint lookup map[uint]unsafe.Pointer } // Ref registers the given pointer and returns a corresponding id that can be // used to retrieve it later. func (p *ptrProxy) Ref(ptr unsafe.Pointer) C.uint { p.Lock() id := p.count p.count++ p.lookup[id] = ptr p.Unlock() return C.uint(id) } // Deref takes an id and returns the corresponding pointer if it exists. func (p *ptrProxy) Deref(id C.uint) (unsafe.Pointer, bool) { p.Lock() val, ok := p.lookup[uint(id)] p.Unlock() return val, ok } // Free releases a registered pointer by its id. func (p *ptrProxy) Free(id C.uint) { p.Lock() delete(p.lookup, uint(id)) p.Unlock() } ================================================ FILE: responsewriter.go ================================================ package main // #include import "C" import ( "net/http" "unsafe" ) //export ResponseWriter_Write func ResponseWriter_Write(wPtr C.uint, cbuf *C.char, length C.int) C.int { buf := C.GoBytes(unsafe.Pointer(cbuf), length) w, ok := cpointers.Deref(wPtr) if !ok { return C.EOF } n, err := (*(*http.ResponseWriter)(w)).Write(buf) if err != nil { return C.EOF } return C.int(n) } //export ResponseWriter_WriteHeader func ResponseWriter_WriteHeader(wPtr C.uint, header C.int) { w, ok := cpointers.Deref(wPtr) if !ok { return } (*(*http.ResponseWriter)(w)).WriteHeader(int(header)) }