/
githubmirror
/
libmicrohttpd
Обзор
Документация
Войти
/
githubmirror
/
libmicrohttpd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
doc/chapters/upgrade.inc
422 строки
18 KB
Christian Grothoff
add tutorials for #4944 and #5085
28 июл 2026, 20:46
Не верифицирован
28 июл 2026, 20:46
7d8c068
Код
Авторство
О чём код?
HTTP is a request/response protocol: the client asks, the server answers, and the connection is either closed or reused for the next request. Some applications need something else entirely --- a bidirectional byte stream that neither side has to poll for. Rather than opening a second connection on another port, @emph{RFC 9110} lets a client ask the server to switch the already established connection over to a different protocol. The client sends an ordinary request carrying an @code{Upgrade:} header field, and if the server agrees it answers with the status code @code{101 Switching Protocols}. Once that response has gone out, HTTP is over: the two sides simply talk whatever protocol they agreed on over the same TCP connection. Since version 0.9.52 @emph{libmicrohttpd} supports this protocol switch. The API is deliberately protocol-agnostic --- @emph{MHD} performs the HTTP part of the handshake and then hands you the socket. What you send over that socket afterwards is entirely your business; @emph{MHD} neither parses nor generates a single byte of it any more. The best known user of this mechanism is the Websocket protocol (@emph{RFC 6455}), which uses the upgrade API as a means to an end and then needs a good deal of machinery of its own for framing, masking and the closing handshake. This chapter takes the opposite approach: it shows the upgrade API on its own, with a protocol so simple that no helper library is needed. Our server switches to a line-based echo protocol: every line the client sends is sent straight back, until the client sends the line @code{QUIT}. @heading Enabling upgrades Upgrading is not allowed by default, because handing raw sockets to application code has consequences the library cannot control. You opt in with the @code{MHD_ALLOW_UPGRADE} flag: @verbatim daemon = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD | MHD_ALLOW_UPGRADE | MHD_USE_ERROR_LOG, PORT, NULL, NULL, &access_handler, NULL, MHD_OPTION_END); @end verbatim @noindent The choice of threading mode matters more here than anywhere else in this tutorial, because it decides whether your code is allowed to block. With @code{MHD_USE_THREAD_PER_CONNECTION} every connection already has a thread of its own, and that thread is the one that will run your upgrade callback. Blocking in it stalls exactly one client, so you may write the straightforward loop of @code{recv()} and @code{send()} calls that everybody has in their head. This is what makes the example in this chapter short enough to read in one go. Without it --- that is, with a single internal polling thread, a thread pool, epoll, or an external event loop --- your callback runs @emph{inside} @emph{MHD}'s event loop. Blocking there freezes every other connection the same loop is serving. In that case the callback must return immediately, and you have to register the socket in your own event loop (or hand it to a thread you started yourself) and drive the conversation from there. @heading Deciding whether to upgrade A client that wants to switch protocols says so twice: it names the protocol in the @code{Upgrade:} header field, and it lists the token @code{Upgrade} in the @code{Connection:} header field. Both fields are comma-separated lists, so a browser may well send @code{Connection: keep-alive, Upgrade}. Comparing the whole field value against the string @code{"Upgrade"} would reject such a request; the example therefore contains a small helper @code{has_token()} that walks the list. Note also that a protocol switch is a HTTP/1.1 feature; @code{MHD_queue_response()} will refuse an upgrade response on a HTTP/1.0 connection. @verbatim if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) || (0 != strcmp (version, MHD_HTTP_VERSION_1_1)) || (! has_token (MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION), "Upgrade")) || (! has_token (MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_UPGRADE), PROTOCOL)) ) return answer_plain (connection, MHD_HTTP_UPGRADE_REQUIRED, PAGE_UPGRADE_REQUIRED, 1); @end verbatim @noindent Refusing is not an error condition: the client asked a question and gets an ordinary HTTP answer. Which answer depends on who is at fault. If the request is malformed --- for instance an @code{Upgrade:} field without the matching @code{Connection:} token --- @code{MHD_HTTP_BAD_REQUEST} is appropriate. If the request is perfectly well-formed but simply does not speak the protocol this resource requires, @code{MHD_HTTP_UPGRADE_REQUIRED} (426) is the better answer, and it should carry an @code{Upgrade:} header field naming the protocol the server would have accepted. Our example folds both cases into a 426 for brevity. Either way you queue a perfectly normal response and the connection stays a HTTP connection. @heading Creating the upgrade response If we do want to switch, we create a special response object with @code{MHD_create_response_for_upgrade()}. It takes the callback that will receive the socket and a closure pointer for it, and returns @code{NULL} on error. The response has no body --- it is a header and nothing else. @verbatim response = MHD_create_response_for_upgrade (&upgrade_handler, NULL); if (NULL == response) return MHD_NO; MHD_add_response_header (response, MHD_HTTP_HEADER_UPGRADE, PROTOCOL); ret = MHD_queue_response (connection, MHD_HTTP_SWITCHING_PROTOCOLS, response); MHD_destroy_response (response); return ret; @end verbatim @noindent Two details are easy to get wrong here. The first is the @code{Connection: Upgrade} response header field, and the surprise is that you must @emph{not} add it. @code{MHD_create_response_for_upgrade()} puts it into the response itself, and @code{MHD_queue_response()} then verifies that it is still there and still contains the @code{upgrade} token before it accepts the response. @emph{MHD} does merge repeated @code{Connection:} header fields into a single field rather than emitting two of them, but it only de-duplicates the @code{close} token, so adding @code{Upgrade} a second time puts @code{Connection: Upgrade, Upgrade} on the wire. Clients tolerate that, but there is no reason to send it. What @emph{MHD} does @emph{not} invent for you is the @code{Upgrade:} field naming the new protocol --- that one is yours to add, since only you know what you are switching to. Do not forget it: @code{MHD_queue_response()} does not check for it, so in a normal build the client simply receives a 101 that fails to say what it switched to, while a library built with assertions enabled aborts when it performs the switch. The second is the status code. @code{MHD_queue_response()} rejects an upgrade response with any status other than @code{MHD_HTTP_SWITCHING_PROTOCOLS}, and conversely it rejects status 101 combined with a response that is not an upgrade response. On any of these mistakes it returns @code{MHD_NO} and, with @code{MHD_USE_ERROR_LOG} enabled, explains itself on @code{stderr}; this is by far the quickest way to debug a handshake that does not happen. The response object itself follows the usual rules. @code{MHD_queue_response()} takes its own reference, so calling @code{MHD_destroy_response()} immediately afterwards is correct and is what all the other examples in this tutorial do. The object is only really freed once the last connection using it is done with it, which for an upgrade means after the callback has returned. As with any other response you may build one upgrade response and queue it for many connections; the callback is then simply invoked once per connection. @heading The upgrade callback After the 101 response has been written to the socket, @emph{MHD} calls the @code{MHD_UpgradeHandler} you registered: @verbatim static void upgrade_handler (void *cls, struct MHD_Connection *connection, void *req_cls, const char *extra_in, size_t extra_in_size, MHD_socket sock, struct MHD_UpgradeResponseHandle *urh) @end verbatim @noindent @table @code @item cls The closure that was passed to @code{MHD_create_response_for_upgrade()}. Since the same response object may serve many connections, this is per-@emph{response} state, not per-connection state. @item connection The original HTTP connection. It is handed over so that you can have a last look at the request --- @code{MHD_lookup_connection_value()} still works here, which is how a real protocol picks up, say, a subprotocol name or an authentication token. Do not queue anything on it any more. @item req_cls Whatever your access handler left in @code{*req_cls}. This is the natural place to put per-connection state that the access handler already computed. @item extra_in / extra_in_size Bytes of your new protocol that the client sent immediately behind the request header, and that @emph{MHD} therefore already read off the socket while parsing the request. They are @emph{gone} from the socket: if you go straight to @code{recv()} you silently lose them. Process this buffer first, exactly as if you had read it yourself. The buffer belongs to @emph{MHD} and is only valid for the duration of the call, so copy anything you want to keep. A client that waits for the 101 before it starts talking will produce @code{extra_in_size == 0}, which is why this argument is so easy to overlook and so annoying when it finally bites. @item sock The socket. For a plain HTTP connection this is the connection to the client; for HTTPS it is one end of a socketpair with @emph{MHD} relaying the TLS traffic, so socket options and @code{getpeername()} will not give you what you expect. Read and write it as you please, and you may call @code{shutdown()} on it, but you must never call @code{close()}. @item urh The handle for @code{MHD_upgrade_action()}, described below. @end table The loop itself is then unremarkable, apart from consuming @code{extra_in} before the first @code{recv()}: @verbatim make_blocking (sock); es.line_len = 0; done = echo_feed (sock, &es, extra_in, extra_in_size); while (0 == done) { got = recv (sock, buf, sizeof (buf), 0); if (0 > got) { if (EINTR == errno) continue; break; /* read error */ } if (0 == got) break; /* client closed the connection */ done = echo_feed (sock, &es, buf, (size_t) got); } MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE); @end verbatim @noindent @heading Blocking or not blocking The call to @code{make_blocking()} deserves a warning, because the current behaviour is the opposite of what the threading modes suggest. @emph{MHD} puts every accepted client socket into non-blocking mode, and it does so regardless of the threading mode --- in fact the library refuses to perform the switch at all, logging @code{Cannot execute "upgrade" as the socket is in the blocking mode}, if it finds the socket blocking. So even under @code{MHD_USE_THREAD_PER_CONNECTION}, where you are perfectly entitled to block, the socket you are handed is non-blocking and a @code{recv()} on an idle connection returns @code{-1} with @code{EAGAIN} instead of waiting. If you want the simple blocking loop shown above, you therefore have to ask for it. The example does so with a small @code{make_blocking()} helper --- @code{fcntl()} clearing @code{O_NONBLOCK} on POSIX systems, @code{ioctlsocket()} on W32 --- which is the only operating-system-dependent code in the whole program. Under any other threading mode you leave the socket alone, keep it non-blocking, and add it to your own event loop instead. @heading Closing the connection The last obligation of the callback is to hand the socket back: @verbatim MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE); @end verbatim @noindent This is not a formality. An upgraded connection is put into an internal ``suspended'' state, and it stays there --- socket open, connection structure allocated, slot counted against the connection limit --- until the application says it is done. Only @code{MHD_UPGRADE_ACTION_CLOSE} says that. Calling @code{close()} on the socket yourself does not, and additionally leaves @emph{MHD} holding a file descriptor number that the operating system may already have handed to somebody else. Call the action exactly once, on every path out of the callback, including the error paths. @code{MHD_upgrade_action()} takes two further actions in the current release, @code{MHD_UPGRADE_ACTION_CORK_ON} and @code{MHD_UPGRADE_ACTION_CORK_OFF}. They switch @code{TCP_CORK} (or @code{TCP_NOPUSH}) on the underlying socket so that several small writes can be coalesced into a single segment. They are a hint, not a guarantee: the call returns @code{MHD_NO} if the platform has no such option or if the socket is not a TCP socket, which is the normal case for HTTPS connections. The header file marks this part of the API as not yet finalised, so do not build anything load-bearing on it. @heading Remarks @code{MHD_stop_daemon()} does @emph{not} interrupt an upgraded connection. It marks the connection as closed internally and logs @code{Initiated daemon shutdown while "upgraded" connection was not closed}, but it neither shuts down nor closes the socket --- and then it waits for the connection's thread, which means it blocks until your callback has returned. A callback parked in a blocking @code{recv()} therefore hangs the shutdown for as long as the client keeps the connection open. Any server that wants to terminate on demand needs its own way out of that loop: a global shutdown flag combined with a @code{shutdown()} on the sockets you are holding, a self-pipe added to a @code{select()} in the loop, or a receive timeout on the socket. Our example sidesteps the problem by only stopping the daemon when the user presses return at a moment when nothing is connected. Error handling in the callback is entirely yours. @emph{MHD} will not tell you that a client vanished and will not time an upgraded connection out --- @code{MHD_OPTION_CONNECTION_TIMEOUT} stops applying the moment the connection is upgraded. Treat @code{0} from @code{recv()} as a clean disconnect and a negative return as a broken one, and remember that a peer which disappears without a FIN produces neither until the TCP stack gives up, which can take a very long time. Whatever happens, end by calling @code{MHD_upgrade_action()} with @code{MHD_UPGRADE_ACTION_CLOSE}. Finally, keep in mind that an upgraded connection is a connection that @emph{MHD} can no longer recycle, and that intermediaries are entitled to be unhelpful: @code{Upgrade:} is listed in @code{Connection:} and is therefore a hop-by-hop header field. A proxy that does not understand your protocol is allowed to drop it, in which case your server never sees an upgrade request at all and the client gets an ordinary HTTP response where it expected a 101. This is the main reason why deployed protocols on top of this mechanism --- Websockets above all --- make the handshake verifiable by both ends instead of trusting that the request arrived intact. The complete program is available as @code{upgrade.c} in the @code{examples} section. Start it and talk to it with any tool that lets you type raw bytes at a socket --- @code{telnet localhost 8888} will do --- by sending @verbatim GET / HTTP/1.1 Host: localhost Connection: Upgrade Upgrade: echo @end verbatim @noindent followed by an empty line. The server answers with @verbatim HTTP/1.1 101 Switching Protocols Date: ... Connection: Upgrade Upgrade: echo @end verbatim @noindent and from then on echoes every line back until you type @code{QUIT}. Note the absence of a @code{Content-Length:} field: a 101 response has no body, and everything after the empty line already belongs to the new protocol. For a real-world protocol built on exactly this API, read @emph{RFC 6455} and note how much of it exists only to make the handshake and the framing verifiable --- none of which the upgrade API itself has an opinion about. @heading Exercises @itemize @bullet @item Point a browser at the server. The browser sends a plain @code{GET /} and receives the @code{426} page. Now add a second resource that serves a small HTML page and switch the upgrade to a different URL, so that the same server can be both browsed and upgraded. @item Make the echo server useful: keep a list of the sockets of all upgraded connections and forward every line one client sends to all the others, turning the example into a minimal chat server. You will need a mutex around the list, and you will discover why the closing rules matter --- a socket must leave the list before its callback returns. @item Fix the shutdown behaviour discussed in the remarks. Add a global ``quitting'' flag and a list of active sockets, have @code{main()} set the flag and call @code{shutdown (sock, SHUT_RDWR)} on every entry before it calls @code{MHD_stop_daemon()}, and verify that the daemon now stops even while a client is connected and silent. @item Change the daemon flags to plain @code{MHD_USE_INTERNAL_POLLING_THREAD} without @code{MHD_USE_THREAD_PER_CONNECTION} and observe that the server now serves exactly one upgraded client and stops answering everybody else. Then repair it: leave the socket non-blocking, start a thread from within the callback, and return immediately. @item Have the client send a line longer than @code{LINE_SIZE}. The example silently drops the excess. What should a real protocol do instead, and which status code would you have used if the problem had been detectable during the handshake? @end itemize