Given all the input on this thread, I arrive at the following pseudo code: 1. Post Read Request Hook: if (Upgrade: request header present) { collect protocol proposals; ps = protocol with highest preference from proposals; if (ps && ps != current) { status = switch protocol(phase => post_read); if (status == APR_EOF) { close connection; } else if (status == APR_EAGAIN) { // protocol switch wants to be called later before handler if (request is "OPTIONS *") { // TODO: invoke again with (phase => handler)? } request->upgrade_protocol = ps; } else { // continue, HTTP/1 processing. May happen on TLS Upgrade // or if the protocol managed to switch back to HTTP/1 // process other stati than APR_SUCCESS as usual } } else if ("http/1.1" not in Protocols) { answer "505 Not Supported"; } } else if ("http/1.1" not in Protocols) { answer "426 Upgrade Required"; }
2. Handler Hook (First or just before): if (r->upgrade_protocol) { status = switch protocol(phase => handler); if (status == APR_EOF) { close connection; } else { // continue, HTTP/1 processing. May happen on TLS Upgrade // or if the protocol managed to switch back to HTTP/1 // process other stati than APR_SUCCESS as usual } } 3. Common protocol switch hook behavior: apr_status_t process_body(apr_bucket_brigade *bb, void *ctx) { // brigade contains unchunked body data of request // may be called several times. EOS bucket in brigade // indicates end of body. // If request has no body, will be called once with EOS bucket. } int xxx_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol, int phase) { apr_status_t stats; if (strcmp(my_protocol, protocol)) { return DECLINED; } if (need_handler && phase != handler) { return APR_EAGAIN; } status = ap_upgrade_request(r, protocol, phase, my_additional_header, process_body); if (status != APR_SUCCESS) { // any type of error, could also fail if NULL is passed instead of // &process_body and request has body return status; } // c->output_filters can be written // c->input_filters can be read, will pass data to process_body() until body is done, // then return data up the filter chain. process protocol; return APR_EOF; }