Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web servers - and how I created my own one

Avatar for Igor Afonov Igor Afonov
January 21, 2012

Web servers - and how I created my own one

Coffe and Code Donetsk Jan 2012

Avatar for Igor Afonov

Igor Afonov

January 21, 2012
Tweet

More Decks by Igor Afonov

Other Decks in Programming

Transcript

  1. HTTP POST /files/new/isubjkzdaeqtfhmycrnwlpvgxo HTTP/1.1 Host: 127.0.0.1:31337 Connection: keep-alive Content-Length: 219

    Origin: http://127.0.0.1:31337 Accept-Encoding: gzip,deflate Accept-Language: en-US Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 ------WebKitFormBoundaryVreBnvR3qKu3B5Iv Content-Disposition: form-data; name="file"; filename="smally" Content-Type: application/octet-stream Very small file. ------WebKitFormBoundaryVreBnvR3qKu3B5Iv--
  2. Lifecycle • Setup networking & signal handlers • Accept connection

    • Parse & process request • Serialize response • Send it back
  3. Networking • socket • bind • listen • accept •

    send/recv/sendfile* • close do the job POSIX
  4. Non-blocking IO • kqueue/poll/epoll • libevent/libev/libuv • Reactor/Notifier/Dispatcher pattern •

    Works good for web sockets & persistent connections (Nginx, node.js, tornado, event machine...)
  5. Cosmonaut • Blocking (At least for now) • Simple &

    straightforward • Made from tiny isolated modules • Fast, not so slow (Implemented in C) • 3 weeks old
  6. Features • Serves static content (via sendfile) • Parses multipart

    data • Basic routing • Provides simple API • Tiny memory footprint
  7. Architecture • Main process forks workers for each request •

    Forking is fast & reliable with copy-on-write • ~10K ram per request • Context switching is expensive :( • ~600 concurrent processes max on Xeon
  8. C & OOP module.h typedef struct module module; typedef struct

    module_state module_state; struct module { void *public_data; polymorfic *polymorfic; module_state *_s; }; module *module_init(...); void module_free(module *m); int module_do_something(module *m);
  9. Memory Management • Allocate & free at the same level

    • Allocate & free at the same module • Use Valgrind • Be careful
  10. Parsing HTTP • Server is set of parsers • Callback

    based • Must work with chunks of data • Stateful • No buffering • No bugs
  11. API #include <cosmonaut/cosmonaut.h> void action_index(http_request *request, http_response *response) { render_text(response,

    "Hello world"); } void configure() { mount("/", action_index); } int main(int argc, char *argv[]) { return cosmonaut_start(argc, argv, configure); } Hello World
  12. API struct http_request { url *url; headers_map *headers; params_map *params;

    configuration *configuration; route *route; void *data; char *uid; }; char *c_type = headers_map_get(request->headers, "Content-Type"); Request
  13. API typedef struct http_response { int code; char *header_summary; char

    *file_path; char *content_type; int content_length; headers_map* headers; char* raw_response; } http_response; render_file(http_response *response, const char *path); render_text(http_response *response, const char *text); render_json(http_response *response, const char *json); Response
  14. API mount("/photos/(:id)", action); void action(http_request *request, http_response *response) { int

    photo_id = params_map_get(request->params, "id")->val; ... } Routing pt.2
  15. API mount("/patients/(:id)/(:action)", rest_application); void rest_application(http_request *request, http_response *response) { char

    *action = params_map_get(request->params, "action")->val; int patient_id = params_map_get(request->params, "id")->val; process_data(patient, action, response); } /patients/1/new // params: {id => 1, action => "new"} /patients/1/show // params: {id => 1, action => "show"} /patients/1/edit // params: {id => 1, action => "edit"} /patients/1/delete // params: {id => 1, action => "delete"} Routing pt.3
  16. Testing Feature: Routing and params processing Scenario Outline: Requesting action

    mounted to '/photos/(:id)' When I request "<path>" Then I should get "<expected_response>" Examples: | path | expected_response | | /photos/1 | [id:1] | | /photos/word | [id:word] | | /photos/1/somth | Not Found | | /photos/1/ | Not Found | | /photos1/1 | Not Found |
  17. Testing When /^I request "([^"]*)"$/ do |path| begin @response =

    RestClient.get("http://127.0.0.1:31337#{path}") rescue Exception => e @client_exception = e end end Then /^I should get "([^"]*)"$/ do |body| response_body = @client_exception.nil? ? @response.to_s : @client_exception.http_body response_body.should == body end
  18. Sample Application • Supports file uploads/downloads • Supports tracking of

    upload progress • Uses redis for persistence & IPC https://github.com/iafonov/uploader_app