{"id":13732190,"url":"https://github.com/tom-seddon/yhs","last_synced_at":"2025-12-24T22:47:48.660Z","repository":{"id":3486467,"uuid":"4542243","full_name":"tom-seddon/yhs","owner":"tom-seddon","description":"Embeddable HTTP server.","archived":false,"fork":false,"pushed_at":"2018-05-07T21:10:05.000Z","size":456,"stargazers_count":80,"open_issues_count":2,"forks_count":13,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-05-02T19:22:02.272Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tom-seddon.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-06-04T01:41:17.000Z","updated_at":"2024-04-11T17:54:56.000Z","dependencies_parsed_at":"2022-09-07T09:11:41.604Z","dependency_job_id":null,"html_url":"https://github.com/tom-seddon/yhs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tom-seddon%2Fyhs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tom-seddon%2Fyhs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tom-seddon%2Fyhs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tom-seddon%2Fyhs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tom-seddon","download_url":"https://codeload.github.com/tom-seddon/yhs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213757424,"owners_count":15634176,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T02:01:48.742Z","updated_at":"2025-12-24T22:47:48.633Z","avatar_url":"https://github.com/tom-seddon.png","language":"C","funding_links":[],"categories":["Networking"],"sub_categories":[],"readme":"#+OPTIONS: toc:nil num:nil author:nil email:nil creator:nil timestamp:nil ^:nil\n#+TITLE: yocto HTTP server\n\nThe yocto HTTP server is a small embeddable web server, with a\nconvenient public domain licence. Use it to add a web server to your\nprogram for debugging, introspection or remote control.\n\nYou specify the paths of \"files\" and \"folders\" you want to make\navailable, and callbacks to be called when they are requested, and the\nyocto HTTP server handles the rest. When your callback is called, you\ncan use convenient stdio-style functions to send text or binary data\nto the browser, or transmit image data.\n\nOf course, if you just want some files serving from folders on disk,\nthe yocto HTTP server will do that.\n\nAlso, WebSockets.\n\nThe yocto HTTP server has been written for ease of embedding and ease\nof use, under the assumption that it will be used as a development and\ndebugging aid. Security and performance were not design goals.\n\n* Installation\n\niOS, Mac OS X and Windows (VC++) are supported. It can be built as\nC89, C99 or C++.\n\n1. Add =yhs.c= and =yhs.h= to your project;\n\n2. Include =yhs.h= in files that need it;\n\n3. Add function calls as described below;\n\n4. Add =#ifdef= (etc.) to make very sure you won't ship with it\n   running;\n\n5. PROFIT.\n\n* Use\n\nThis file provides a conversational overview. Please consult the\nheader file as well.\n\n** Start, update and stop server\n\nA particular server (serving a particular tree of \"files\" on a\nparticular port) is represented by a =yhsServer= pointer:\n\n: yhsServer *server;\n\nCreate one using =yhs_new_server=, supplying port:\n\n: server = yhs_new_server(80);\n\nYou can name your server, if you like. Its name will appear in any\nerror pages.\n\n: yhs_set_server_name(server,\"my amazing server\");\n\nEach time round your main loop, call =yhs_update= to keep the server\nticking over:\n\n: yhs_update(server);\n\nWhen you're done, call =yhs_delete_server= to free up the server and\nits resources:\n\n: yhs_delete_server(server);\n: server=NULL;\n\n** Adding things to serve\n\nUse =yhs_add_res_path_handler= to add a callback (see below) for a\nparticular path:\n\n: yhs_add_res_path_handler(server,\"/res/\",\u0026handle_root,NULL);\n\nThe argument for =context= is stored and made available to the\ncallback.\n\nPaths not ending in =/= are considered files, and their callback will\nbe called when a request is made for that exact path.\n\nPaths ending in =/= are considered folders, meaning the callback will\nbe called for any file in that folder (at whatever depth), if there\nisn't a closer-matching folder or file handler for it.\n\nIf there's no handler added for the root folder =/=, =GET= requests\nfor =/= will be responded to automatically with a contents page.\n\nThe server will respond to any other unhandled path with a 404 page.\n\n** Serving things\n\nThe handler callback has the following signature:\n\n: extern \"C\" typedef void (*yhsResPathHandlerFn)(yhsRequest *re);\n\n=re= points to the (opaque) request object. There are various\nfunctions to get details about the request:\n\n- =yhs_get_path= retrieves the specified path, and\n  =yhs_get_path_handler_relative= retrieves the part that's relative\n  to the path supplied to =yhs_add_res_path_handler=.\n\n- =yhs_get_method= and =yhs_get_method_str= retrieve the HTTP\n  method. =yhs_get_method= returns one of the values from the (not\n  exhaustive) =yhsMethod= enum, and =yhs_get_method_str= returns the\n  actual method name string.\n\n- =yhs_find_header_field= allows the request header fields to be\n  queried.\n\nYou can also use =yhs_get_handler_context= and =yhs_get_handler_path=\nto retrieve the values supplied to =yhs_add_res_path_handler=.\n\nOn entry to the callback, any content is available for reading (if you\nwant it), and the server is ready for your callback to provide a\nresponse, as described below.\n\nOnce you have sent the response, just return from the callback and\nappropriate action will be taken automatically. If your callback\ndoesn't provide any response, the server will automatically provide a\n404 page.\n\n(You can respond to a =HEAD= request in exactly the same way as a\n=GET= request. The server checks for =HEAD= specially, and will\ndiscard any response body in that case, leaving just the headers.)\n\n*** Data response\n\nUse =yhs_begin_data_response= to start a data response, supplying MIME type\nof data being sent:\n\n: yhs_begin_data_response(re,\"text/html\");\n\nThen use =yhs_text= (works like =printf=) to send raw text:\n\n: yhs_text(re,\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eHello\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003cp\u003e%d\u003c/p\u003e\u003c/body\u003e\u003c/html\u003e\",rand());\n\nAlso available are =yhs_textv= (works like =vprintf=), =yhs_text=\n(works like =fputs=), =yhs_data= (works a bit like =fwrite=), and\n=yhs_data_byte= (works a bit like =fputc=).\n\nIf you're responding with HTML, there are a set of convenience\nfunctions, =yhs_html_text*=, which can add in HTML escapes and\noptionally replace =\\n= with =\u003cBR\u003e=.\n\n: yhs_html_text(re,YHS_HEF_BR,random_text);\n\nThese functions perform a bit of buffering, so don't be afraid to\nwrite single bytes or chars.\n\nBetween calling =yhs_begin_data_response= and =yhs_text= (or similar), you\ncan add extra HTTP header fields to the response using\n=yhs_header_field=:\n\n: yhs_header_field(re,\"X-Powered-By\",\"C\");\n\n(=yhs_begin_data_response= will already have added an appropriate\n=Content-Type= field.)\n\n*** Image response\n\nUse =yhs_begin_image_response= to start an image response. Supply width,\nheight and bytes per pixel of image:\n\n: yhs_begin_image_response(re,256,256,3);\n\nThen for each pixel -- and you must supply every pixel -- call\n=yhs_pixel= to specify red, green, blue and alpha:\n\n: for(int y=0;y\u003c256;++y) {\n:     for(int x=0;x\u003c256;++x)\n:         yhs_pixel(re,rand()\u0026255,rand()\u0026255,rand()\u0026255,255);\n: }\n\nDo please note that the PNGs are not compressed.\n\nBetween calling =yhs_begin_image_response= and =yhs_text= (or similar), you\ncan add extra HTTP header fields to the response using\n=yhs_header_field=:\n\n: yhs_header_field(re,\"X-Powered-By\",\"C\");\n\n(=yhs_begin_image_response= will already have added an appropriate\n=Content-Type= field.)\n\n*** Error response\n\nCall =yhs_error_response= to generate an HTTP error page. Provide\nthe HTTP status line, e.g., \"200 OK\".\n\n*** 303 See Other response\n\nUse =yhs_see_other_response= to direct the browser to =GET= a\ndifferent URL.\n\n*** Serving a tree of files\n\nThe server is primarily designed for serving data using the callbacks,\nbut you can use the supplied =yhs_file_server_handler= handler to\nsupply a tree of local files. You might use this for icons, say, or\nJavascript.\n\nWhen adding the file server handler, supply the local path as the\ncontext pointer:\n\n: yhs_add_res_path_handler(server,\"/resources/\",\u0026yhs_file_server_handler,(void *)\"./web_resources/\");\n\nIf a folder is requested rather than a file, the server will respond\nwith a simple files listing page.\n\n** Deferred responses\n\nYou may want to put off responding to a request, if it can't be\nconveniently responded to in the middle of the server update. You can\ncall =yhs_defer_response= to do this.\n\nRequests with deferred responses are held in a list, so you can work\nthrough them later. You can maintain one list of all such requests, or\nhave multiple lists.\n\nEach list is represented by a =yhsRequest *=, holding a pointer to the\nhead. It should start out NULL.\n\n: yhsRequest *list=NULL;\n\nTo defer a response, pass the request you're dealing with, and a\npointer to the list head pointer:\n\n: yhs_defer_response(re,\u0026list);\n\nThis allocates a copy of the current request, adds it to the list, and\ninvalidates =*re=. (=yhs_defer_response= may fail and return 0, if the\nallocation fails; in that case, the list will be unchanged, and the\nserver will end up producing a 404. So most of the time, you probably\nwon't need to check.)\n\nThen later, work through the list and make progress with each response\nusing the functions above. Then, to advance your current item pointer\nto the next request in the list, use =yhs_next_request_ptr= to leave\nthe response in progress or =yhs_end_deferred_response= to finish it\nup and remove it from the list.\n\nThe expected code is along these lines:\n\n: yhsRequest **cur=\u0026list;\n: while(*cur) {\n:     /* do stuff to **cur */\n:     if(/* finished with **cur */)\n:         yhs_end_deferred_response(cur);\n:     else\n:         yhs_next_request_ptr(cur);\n: }\n\n** Content\n\nIf the request has content associated with it, use =yhs_get_content=\nto retrieve it. Check for associated content by looking for the\n=Content-Length= header field by hand, or use\n=yhs_get_content_details= to do the check. =yhs_get_content_details=\nwill retrieve =Content-Length= as an =int=, and find any\n=Content-Type= field supplied too.\n\nYou can retrieve the content all in one go, or in parts.\n\n** Forms\n\nHelpers are provided for processing data from =POST= method forms in\n=application/x-www-form-urlencoded= format. (=GET= forms, and\n=multipart/form-data=, are not specifically catered for.)\n\nIn the handler, use =yhs_read_form_content=:\n\n: int is_form_data_ok=yhs_read_form_content(re);\n: if(!is_form_data_ok) {\n:     /* error (probably unlikely) */\n:     return;\n: }\n\nThis allocates some memory to save off the form data. This memory is\nfreed automatically when the response finishes.\n\nYou can (try to) retrieve a control's value by control name, using\n=yhs_find_control_value=:\n\n: const char *value=yhs_find_control_value(re,\"value name\");\n\nThe result is =NULL= if the value doesn't exist.\n\nYou can also iterate through all the names and values available:\n\n: for(size_t i=0;i\u003cyhs_get_num_controls(re);++i) {\n:     const char *name=yhs_get_control_name(re,i);\n:     const char *value=yhs_get_control_value(re,i);\n: }\n\nThe pointers point into the data set up by =yhs_read_form_content=.\nThe pointed-to data must be copied if it is to be kept past the end of\nthe response.\n\n** Handler configuration\n\nAfter adding a handler for a path, you can configure it. \n\nUse =yhs_add_to_toc= to add the handler to the contents page. A link\nis provided to the handler's path; by default, the text of the link is\nthe path too, but you can use =yhs_set_handler_description= to provide\nsomething friendlier.\n\nUse =yhs_set_valid_methods= to set the valid HTTP methods for the\npath. The default valid methods are =GET= and =HEAD= only. The server\nwill ignore any requests for a path using an invalid method (so that\nmost handlers won't have to check the method).\n\nThe configure functions return the supplied handler, so you can do\neverything on one line:\n\n: yhs_add_to_toc(yhs_set_handler_description(\"test handler\",yhs_add_res_path_handler(server,\"/test\",\u0026test_func,NULL)));\n\n** WebSockets\n\nyhs supports WebSockets as per RFC 6455\n(http://tools.ietf.org/html/rfc6455).\n\nyhs passes the AutobahnTestsuite Websocket tests\n(http://autobahn.ws/testsuite), suggesting that it actually works.\n\n*** WebSocket connections\n\nTo set up a potential WebSocket connection, use\n=yhs_set_valid_methods= to add =YHS_METHOD_WEBSOCKET= as a valid\nmethod for the handler.\n\n: yhs_set_valid_methods(YHS_METHOD_WEBSOCKET,yhs_add_res_path_handler(server,\"/ws\",\u0026ws_func,NULL));\n\nIn the handler, =yhs_get_method= will return =YHS_METHOD_WEBSOCKET= if\nthere is a WebSocket connection attempt being made. Use\n=yhs_accept_websocket= to approve it, and put the connection into\nWebSocket mode.\n\nOnce the connection is in WebSocket mode, call =yhs_is_websocket_open=\nto see if the connection is still open. The WebSocket MUST (their\nwords, not mine!) be closed at the slightest provocation, so it might\nbecome closed unexpectedly.\n\nWebSocket connections are expected to be deferred, but there's no\nobligation.\n\n*** Receiving WebSocket data\n\nTo receive data on the WebSocket, or try to, use\n=yhs_begin_recv_websocket_frame=. =yhs_begin_recv_websocket_frame= is\nnon-blocking, and will return 1 if there is data waiting, and\noptionally set a variable to indicate whether the incoming frame is\ntext or binary.\n\nOnce =yhs_begin_recv_websocket_frame= returns 1, the data is ready for\nreading, and you are committed to reading it. Use\n=yhs_recv_websocket_data= to do this. =yhs_recv_websocket_data= will\nattempt to fill a buffer with incoming data, stopping when the buffer\nis full, the entire frame has been read, or something else happened\n(some kind of error, or WebSocket closed).\n\n(yhs will automatically handle continuation frames; you can't detect\nthe fragmentation.)\n\nOnce you've read the data, call =yhs_end_recv_websocket_frame= to\nstop. If there is unread data in the frame, it will be silently\ndiscarded.\n\n: int is_text;\n: if(yhs_begin_recv_websocket_frame(re,\u0026is_text)) {\n:     char buf[1000];\n:     size_t n;\n:     if(yhs_recv_websocket_data(re,buf,sizeof buf,\u0026n)) {\n:         /* stuff */\n:     }\n: }\n\n=yhs_recv_websocket_data= will always fill the entire buffer if\nthere's data to fill it with, and will block if required. If the read\nsucceeded, and the size read is less than the size of the buffer, all\nthe data in the frame has been read.\n\n*** Receiving a WebSocket text frame\n\nIf the incoming data is text, yhs still allows you to treat it as a\nsequence of bytes for reading purposes. This means you can read\npartial UTF-8 byte sequences (e.g., if you're receiving 1 byte at a\ntime), leaving you with invalid intermediate UTF-8. So take care.\n\nAdditionally, the UTF-8 data is validated char-by-char rather than\nbyte-by-byte, so you can receive parts of obviously invalid UTF-8 byte\nsequences as well, if yhs has yet to see the entire char to validate\nit. So... take care with that, too.\n\nAll in all, if reading a text frame, you're advised to read the whole\nthing in before doing anything with it. \n\n*** Sending WebSocket data\n\nTo start sending a frame of data, use\n=yhs_begin_send_websocket_frame=, supplying a flag indicating whether\nthe frame is text or binary.\n\nOnce the frame is started, use the various data sending functions\n(=yhs_text*=, =yhs_data*=) to send data. (yhs will fragment the frame\nat its discretion, if necessary.) Then call\n=yhs_end_send_websocket_frame= once done.\n\nIf sending a text frame, it must be valid UTF-8, but yhs doesn't\ncheck, under the assumption that the client will.\n\n*** Closing the WebSocket\n\nIf the request isn't deferred, the WebSocket will be closed when the\nhandler returns; if the request is deferred, use\n=yhs_end_deferred_response= to close it.\n\n* Tweakables\n\nThere are some tweakable macros and constants near the top of the .c\nfile. There's no API for changing these; just edit them using a text\neditor.\n\n** Constants\n\nThe main ones:\n\n- =MAX_REQUEST_SIZE= :: max supported size of HTTP header included in\n     request. Server will return a 500 Internal Server Error if the\n     client exceeds this.\n\n- =MAX_TEXT_LEN= :: size of buffer used for format string\n                    expansion. Affects maximum possible length of\n                    output from yhs_textf and yhs_textv.\n\n- =WRITE_BUF_SIZE= :: size of buffer used when writing, to avoid lots\n     of little =send= socket calls.\n\n=MAX_TEXT_LEN= and =WRITE_BUF_SIZE= contribute to the size of the\n=yhsServer= object; =MAX_REQUEST_SIZE= contributes to the amount of\nstack required by the =yhs_update= call.\n\n** Memory allocation\n\nThere are two malloc macros, =MALLOC= and =FREE=, by default wrapping\n=malloc= and =free= respectively.\n\n** Logging\n\nThere are 3 logging macros, =YHS_DEBUG_MSG=, =YHS_INFO_MSG= and\n=YHS_ERR_MSG=. These are invoked just like printf, and are assumed to\nexpand to a single statement.\n\nBy default, debug and info messages go to =stdout=, and errors go to\n=stderr=.\n\n* Notes\n\n- The server uses blocking sockets and makes blocking socket calls, so\n  =yhs_update= could take pretty much any amount of time, if there's\n  something to do. (=yhs_update= will return =1= if it did anything\n  significant, the idea being that the game avoids playing logic\n  catch-up in this case. No timing is actually performed; this is just\n  a quick hack.)\n\n* TODOs\n\n- 303 would probably be a better default response to a POST than 404.\n\n- Optional integration with miniz or stb_image_write to serve\n  compressed PNGs\n\n- Optional integration with miniz for gzip'd transfers\n\n- Support \"Transfer-Encoding: chunked\"?\n\n- Maybe do something nicer about form content?\n\n- Have the file server handler check for =index.html= and, if present,\n  respond with its contents rather than the files listing?\n\n- Currently does a poor job of handling duplicated header lines\n  conveniently; instead of requiring repeated yhs_find_header_field\n  calls to find them all, it should just join them on receipt (see\n  section 4.2) and provide some API for accessing comma-separated\n  lists as a list as well as the string. C strings :(\n\n- No support for selecting the WebSocket protocol\n\n- Add mutex as appropriate, so deferred connections can be serviced on\n  other threads (caller is responsible for thread safety of the chain\n  but yhs will have to do the next_deferred/prev_deferred list)\n\n- Add some kind of context pointer to a yhsRequest when deferring, so\n  the caller can store some action data or something? (Making it\n  easier to have all of them in one big list, so there's only one\n  mutex for the caller to maintain?)\n\n- =yhs_get_content= and =yhs_recv_websocket_data= should probably be\n  much more similar than they are.\n\n- Is \"yocto\" still appropriate?\n\n* Other embeddable web serving options\n\nIf you disagree with the choices made here, perhaps one of these other\nofferings will be more to your taste.\n\n** mongoose\n\nhttp://code.google.com/p/mongoose/\n\n** libmicrohttpd\n\nhttp://www.gnu.org/software/libmicrohttpd/\n\n** tulrich-testbed\n\nhttp://tu-testbed.svn.sourceforge.net/viewvc/tu-testbed/trunk/tu-testbed/net/\n\n** EasyHTTPD\n\nhttp://sourceforge.net/projects/ehttpd/\n\n** EHS\n\nhttp://ehs.fritz-elfert.de\n\n** poco\n\nhttp://pocoproject.org/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftom-seddon%2Fyhs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftom-seddon%2Fyhs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftom-seddon%2Fyhs/lists"}