On 5/12/20 2:20 PM, [email protected] wrote:
> Author: ylavic
> Date: Tue May 12 12:20:57 2020
> New Revision: 1877646
> 
> URL: http://svn.apache.org/viewvc?rev=1877646&view=rev
> Log:
> mod_proxy_http: handle Upgrade requests and upgraded protocol forwarding.
> 
> If the request Upgrade header matches the worker upgrade= parameter and
> the backend switches the protocol, do the tunneling in mod_proxy_http.
> This allows to keep the protocol to HTTP until the backend really
> switches the protocol, and apply usual output filters.
> 
> When configured to forward Upgrade mechanism, we want the backend to be
> able to announce its Upgrade protocol to the client (e.g. with 426
> Upgrade Required response) and thus forward back the Upgrade header that
> matches the one(s) configured in the worker upgrade= parameter.
> 
> modules/proxy/mod_proxy.h:
> modules/proxy/proxy_util.c:
>     ap_proxy_worker_can_upgrade(): added helper to determine whether a
>     proxy worker is configured to forward an Upgrade protocol.
> 
> include/ap_mmn.h:
>     Bump MMN minor for ap_proxy_worker_can_upgrade().
> 
> modules/proxy/mod_proxy.c:
>     set_worker_param(): handle worker parameter upgrade=ANY as upgrade=*
>     (should the "any" protocol scheme be something some day..).
> 
> modules/proxy/mod_proxy_wstunnel.c:
>     proxy_wstunnel_handler(): use ap_proxy_worker_can_upgrade() to match
>     the Upgrade header. Axe handling of upgrade=NONE, it makes no sense to
>     Upgrade a connection if the client did not ask for it, nor to configure
>     mod_proxy_wstunnel to use a worker with upgrade=NONE by the way.
> 
> modules/proxy/mod_proxy_http.c:
>     proxy_http_req_t: add fields force10 (force HTTP/1.0) and upgrade (value
>     of the Upgrade header sent by the client if it matches the configuration,
>     NULL otherwise).
>     proxy_http_handler(): use ap_proxy_worker_can_upgrade() to determine
>     whether the request is electable for end to end protocol upgrading and set
>     req->upgrade accordingly.
>     terminate_headers(): handle Connection and Upgrade headers to send to the
>     backend, according to req->force10 and req->upgrade set before.
>     ap_proxy_http_prefetch(): use req->force10 and terminate_headers().
>     send_continue_body(): added helper to send the body retained for end to
>     end 100-continue handling.
>     ap_proxy_http_process_response(): use ap_proxy_worker_can_upgrade() to
>     match the response Upgrade header and forward it back if it matches the
>     configured one(s). That is for 101 Switching Protocol obviously but also
>     any other status code which is not overidden, at the backend wish. If the
>     protocol is switching, create a proxy tunnel and run it, using the minimal
>     timeout from the client or backend connection.
> 
> Github: closes #125
> 
> 
> Modified:
>     httpd/httpd/trunk/CHANGES
>     httpd/httpd/trunk/docs/log-message-tags/next-number
>     httpd/httpd/trunk/include/ap_mmn.h
>     httpd/httpd/trunk/modules/proxy/mod_proxy.c
>     httpd/httpd/trunk/modules/proxy/mod_proxy.h
>     httpd/httpd/trunk/modules/proxy/mod_proxy_http.c
>     httpd/httpd/trunk/modules/proxy/mod_proxy_wstunnel.c
>     httpd/httpd/trunk/modules/proxy/proxy_util.c
> 

> Modified: httpd/httpd/trunk/modules/proxy/mod_proxy_http.c
> URL: 
> http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/mod_proxy_http.c?rev=1877646&r1=1877645&r2=1877646&view=diff
> ==============================================================================
> --- httpd/httpd/trunk/modules/proxy/mod_proxy_http.c (original)
> +++ httpd/httpd/trunk/modules/proxy/mod_proxy_http.c Tue May 12 12:20:57 2020

> @@ -1509,6 +1564,7 @@ int ap_proxy_http_process_response(proxy
>              ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
>                            "HTTP: received interim %d response", r->status);
>              if (!policy
> +                    || upgrade


Shouldn't that be (upgrade && proxy_status == HTTP_SWITCHING_PROTOCOLS) in case 
someone sends a upgrade header
without status HTTP_SWITCHING_PROTOCOLS?
Furthermore don't we need to add in back the upgrade header and the Connection: 
upgrade before we send the interim
response with HTTP_SWITCHING_PROTOCOLS?

>                      || (!strcasecmp(policy, "RFC")
>                          && (proxy_status != HTTP_CONTINUE
>                              || (req->expecting_100 = 1)))) {

> @@ -1615,6 +1649,67 @@ int ap_proxy_http_process_response(proxy
>              do_100_continue = 0;
>          }
>  
> +        if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
> +            apr_status_t rv;
> +            proxy_tunnel_rec *tunnel;
> +            apr_interval_time_t client_timeout = -1,
> +                                backend_timeout = -1;
> +
> +            /* If we didn't send the full body yet, do it now */
> +            if (do_100_continue) {
> +                req->expecting_100 = 0;
> +                status = send_continue_body(req);

Can we really have a body that we held back in case the backend sends 
HTTP_SWITCHING_PROTOCOLS?
IMHO if we have a body held back we sent an Expect 100-Continue header with our 
request to the backend
and the backend should have replied with 100-Continue and not with 
HTTP_SWITCHING_PROTOCOLS

> +                if (status != OK) {
> +                    return status;
> +                }
> +            }
> +
> +            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239)
> +                          "HTTP: tunneling protocol %s", upgrade);
> +
> +            rv = ap_proxy_tunnel_create(&tunnel, r, origin, "HTTP");
> +            if (rv != APR_SUCCESS) {
> +                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240)
> +                              "can't create tunnel for %s", upgrade);
> +                return HTTP_INTERNAL_SERVER_ERROR;
> +            }
> +
> +            /* Set timeout to the lowest configured for client or backend */
> +            apr_socket_timeout_get(backend->sock, &backend_timeout);
> +            apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout);
> +            if (backend_timeout >= 0 && backend_timeout < client_timeout) {
> +                tunnel->timeout = backend_timeout;
> +            }
> +            else {
> +                tunnel->timeout = client_timeout;
> +            }
> +
> +            /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout, we
> +             * use a single idle timeout from now on.
> +             */
> +            ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
> +
> +            /* Let proxy tunnel forward everything */
> +            status = ap_proxy_tunnel_run(tunnel);
> +            if (ap_is_HTTP_ERROR(status)) {
> +                /* Tunnel always return HTTP_GATEWAY_TIME_OUT on timeout,
> +                 * but we can differentiate between client and backend here.
> +                 */
> +                if (status == HTTP_GATEWAY_TIME_OUT
> +                        && tunnel->timeout == client_timeout) {
> +                    status = HTTP_REQUEST_TIME_OUT;
> +                }
> +            }
> +            else {
> +                /* Update r->status for custom log */
> +                status = HTTP_SWITCHING_PROTOCOLS;
> +            }
> +            r->status = status;
> +
> +            /* We are done with both connections */
> +            return DONE;
> +        }
> +
>          if (interim_response) {
>              /* Already forwarded above, read next response */
>              continue;
> @@ -1666,6 +1761,12 @@ int ap_proxy_http_process_response(proxy
>              return proxy_status;
>          }
>  
> +        /* Forward back Upgrade header if it matches the configured one(s). 
> */
> +        if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade)) {
> +            apr_table_setn(r->headers_out, "Connection", "Upgrade");
> +            apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, 
> upgrade));
> +        }
> +

Hmm. When do we hit this? When the backend sends HTTP_UPGRADE_REQUIRED? And if 
yes shouldn't we only care in this case?

>          r->sent_bodyct = 1;
>          /*
>           * Is it an HTTP/0.9 response or did we maybe preread the 1st line of

Regards

RĂ¼diger

Reply via email to