| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| |
| *** mod_proxy_uwsgi *** |
| |
| Copyright 2009-2017 Unbit S.a.s. <info@unbit.it> |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| #define APR_WANT_MEMFUNC |
| #define APR_WANT_STRFUNC |
| #include "apr_strings.h" |
| #include "apr_hooks.h" |
| #include "apr_optional_hooks.h" |
| #include "apr_buckets.h" |
| |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_log.h" |
| #include "http_protocol.h" |
| #include "http_request.h" |
| #include "util_script.h" |
| |
| #include "mod_proxy.h" |
| |
| |
| #define UWSGI_SCHEME "uwsgi" |
| #define UWSGI_DEFAULT_PORT 3031 |
| |
| module AP_MODULE_DECLARE_DATA proxy_uwsgi_module; |
| |
| |
| static int uwsgi_canon(request_rec *r, char *url) |
| { |
| char *host, sport[sizeof(":65535")]; |
| const char *err, *path; |
| apr_port_t port = UWSGI_DEFAULT_PORT; |
| |
| if (ap_cstr_casecmpn(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) { |
| return DECLINED; |
| } |
| url += sizeof(UWSGI_SCHEME); /* Keep slashes */ |
| |
| err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); |
| if (err) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10097) |
| "error parsing URL %s: %s", url, err); |
| return HTTP_BAD_REQUEST; |
| } |
| |
| if (port != UWSGI_DEFAULT_PORT) |
| apr_snprintf(sport, sizeof(sport), ":%u", port); |
| else |
| sport[0] = '\0'; |
| |
| if (ap_strchr(host, ':')) { /* if literal IPv6 address */ |
| host = apr_pstrcat(r->pool, "[", host, "]", NULL); |
| } |
| |
| path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, |
| r->proxyreq); |
| if (!path) { |
| return HTTP_BAD_REQUEST; |
| } |
| |
| r->filename = |
| apr_pstrcat(r->pool, "proxy:" UWSGI_SCHEME "://", host, sport, "/", |
| path, NULL); |
| |
| return OK; |
| } |
| |
| |
| static int uwsgi_send(proxy_conn_rec * conn, const char *buf, |
| apr_size_t length, request_rec *r) |
| { |
| apr_status_t rv; |
| apr_size_t written; |
| |
| while (length > 0) { |
| written = length; |
| if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10098) |
| "sending data to %s:%u failed", |
| conn->hostname, conn->port); |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| |
| /* count for stats */ |
| conn->worker->s->transferred += written; |
| buf += written; |
| length -= written; |
| } |
| |
| return OK; |
| } |
| |
| |
| /* |
| * Send uwsgi header block |
| */ |
| static int uwsgi_send_headers(request_rec *r, proxy_conn_rec * conn) |
| { |
| char *buf, *ptr; |
| |
| const apr_array_header_t *env_table; |
| const apr_table_entry_t *env; |
| |
| int j; |
| |
| apr_size_t headerlen = 4; |
| apr_size_t pktsize, keylen, vallen; |
| const char *script_name; |
| const char *path_info; |
| const char *auth; |
| |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| |
| /* |
| this is not a security problem (in Linux) as uWSGI destroy the env memory area readable in /proc |
| and generally if you host untrusted apps in your server and allows them to read others uid /proc/<pid> |
| files you have higher problems... |
| */ |
| auth = apr_table_get(r->headers_in, "Authorization"); |
| if (auth) { |
| apr_table_setn(r->subprocess_env, "HTTP_AUTHORIZATION", auth); |
| } |
| |
| script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME"); |
| path_info = apr_table_get(r->subprocess_env, "PATH_INFO"); |
| |
| if (script_name && path_info) { |
| if (strcmp(path_info, "/")) { |
| apr_table_set(r->subprocess_env, "SCRIPT_NAME", |
| apr_pstrndup(r->pool, script_name, |
| strlen(script_name) - |
| strlen(path_info))); |
| } |
| else { |
| if (!strcmp(script_name, "/")) { |
| apr_table_setn(r->subprocess_env, "SCRIPT_NAME", ""); |
| } |
| } |
| } |
| |
| env_table = apr_table_elts(r->subprocess_env); |
| env = (apr_table_entry_t *) env_table->elts; |
| |
| for (j = 0; j < env_table->nelts; ++j) { |
| headerlen += 2 + strlen(env[j].key) + 2 + (env[j].val ? strlen(env[j].val) : 0); |
| } |
| |
| pktsize = headerlen - 4; |
| if (pktsize > APR_UINT16_MAX) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10259) |
| "can't send headers to %s:%u: packet size too " |
| "large (%" APR_SIZE_T_FMT ")", |
| conn->hostname, conn->port, pktsize); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| ptr = buf = apr_palloc(r->pool, headerlen); |
| |
| ptr += 4; |
| |
| for (j = 0; j < env_table->nelts; ++j) { |
| keylen = strlen(env[j].key); |
| *ptr++ = (apr_byte_t) (keylen & 0xff); |
| *ptr++ = (apr_byte_t) ((keylen >> 8) & 0xff); |
| memcpy(ptr, env[j].key, keylen); |
| ptr += keylen; |
| |
| vallen = env[j].val ? strlen(env[j].val) : 0; |
| *ptr++ = (apr_byte_t) (vallen & 0xff); |
| *ptr++ = (apr_byte_t) ((vallen >> 8) & 0xff); |
| if (env[j].val) { |
| memcpy(ptr, env[j].val, vallen); |
| } |
| ptr += vallen; |
| } |
| |
| buf[0] = 0; |
| buf[1] = (apr_byte_t) (pktsize & 0xff); |
| buf[2] = (apr_byte_t) ((pktsize >> 8) & 0xff); |
| buf[3] = 0; |
| |
| return uwsgi_send(conn, buf, headerlen, r); |
| } |
| |
| |
| static int uwsgi_send_body(request_rec *r, proxy_conn_rec * conn) |
| { |
| if (ap_should_client_block(r)) { |
| char *buf = apr_palloc(r->pool, AP_IOBUFSIZE); |
| int status; |
| long readlen; |
| |
| readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE); |
| while (readlen > 0) { |
| status = uwsgi_send(conn, buf, (apr_size_t)readlen, r); |
| if (status != OK) { |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE); |
| } |
| if (readlen == -1) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10099) |
| "receiving request body failed"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| |
| return OK; |
| } |
| |
| static request_rec *make_fake_req(conn_rec *c, request_rec *r) |
| { |
| apr_pool_t *pool; |
| request_rec *rp; |
| |
| apr_pool_create(&pool, c->pool); |
| apr_pool_tag(pool, "proxy_uwsgi_rp"); |
| |
| rp = apr_pcalloc(pool, sizeof(*r)); |
| |
| rp->pool = pool; |
| rp->status = HTTP_OK; |
| |
| rp->headers_in = apr_table_make(pool, 50); |
| rp->subprocess_env = apr_table_make(pool, 50); |
| rp->headers_out = apr_table_make(pool, 12); |
| rp->err_headers_out = apr_table_make(pool, 5); |
| rp->notes = apr_table_make(pool, 5); |
| |
| rp->server = r->server; |
| rp->log = r->log; |
| rp->proxyreq = r->proxyreq; |
| rp->request_time = r->request_time; |
| rp->connection = c; |
| rp->output_filters = c->output_filters; |
| rp->input_filters = c->input_filters; |
| rp->proto_output_filters = c->output_filters; |
| rp->proto_input_filters = c->input_filters; |
| rp->useragent_ip = c->client_ip; |
| rp->useragent_addr = c->client_addr; |
| |
| rp->request_config = ap_create_request_config(pool); |
| proxy_run_create_req(r, rp); |
| |
| return rp; |
| } |
| |
| static int uwsgi_response(request_rec *r, proxy_conn_rec * backend, |
| proxy_server_conf * conf) |
| { |
| |
| char buffer[HUGE_STRING_LEN]; |
| const char *buf; |
| char *value, *end; |
| char keepchar; |
| int len; |
| int backend_broke = 0; |
| int status_start; |
| int status_end; |
| int finish = 0; |
| conn_rec *c = r->connection; |
| apr_off_t readbytes; |
| apr_status_t rv; |
| apr_bucket *e; |
| apr_read_type_e mode = APR_NONBLOCK_READ; |
| apr_bucket_brigade *pass_bb; |
| apr_bucket_brigade *bb; |
| proxy_dir_conf *dconf; |
| |
| request_rec *rp = make_fake_req(backend->connection, r); |
| rp->proxyreq = PROXYREQ_RESPONSE; |
| |
| bb = apr_brigade_create(r->pool, c->bucket_alloc); |
| pass_bb = apr_brigade_create(r->pool, c->bucket_alloc); |
| |
| len = ap_getline(buffer, sizeof(buffer), rp, 1); |
| |
| if (len <= 0) { |
| /* oops */ |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| backend->worker->s->read += len; |
| |
| if (len >= sizeof(buffer) - 1) { |
| /* oops */ |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| /* Position of http status code */ |
| if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) { |
| status_start = 9; |
| } |
| else if (apr_date_checkmask(buffer, "HTTP/# ###*")) { |
| status_start = 7; |
| } |
| else { |
| /* oops */ |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| status_end = status_start + 3; |
| |
| keepchar = buffer[status_end]; |
| buffer[status_end] = '\0'; |
| r->status = atoi(&buffer[status_start]); |
| |
| if (keepchar != '\0') { |
| buffer[status_end] = keepchar; |
| } |
| else { |
| /* 2616 requires the space in Status-Line; the origin |
| * server may have sent one but ap_rgetline_core will |
| * have stripped it. */ |
| buffer[status_end] = ' '; |
| buffer[status_end + 1] = '\0'; |
| } |
| r->status_line = apr_pstrdup(r->pool, &buffer[status_start]); |
| |
| /* start parsing headers */ |
| while ((len = ap_getline(buffer, sizeof(buffer), rp, 1)) > 0) { |
| value = strchr(buffer, ':'); |
| /* invalid header skip */ |
| if (!value) |
| continue; |
| *value = '\0'; |
| ++value; |
| while (apr_isspace(*value)) |
| ++value; |
| for (end = &value[strlen(value) - 1]; |
| end > value && apr_isspace(*end); --end) |
| *end = '\0'; |
| apr_table_add(r->headers_out, buffer, value); |
| } |
| |
| /* T-E wins over C-L */ |
| if (apr_table_get(r->headers_out, "Transfer-Encoding")) { |
| apr_table_unset(r->headers_out, "Content-Length"); |
| backend->close = 1; |
| } |
| |
| if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { |
| ap_set_content_type(r, apr_pstrdup(r->pool, buf)); |
| } |
| |
| /* honor ProxyErrorOverride and ErrorDocument */ |
| #if AP_MODULE_MAGIC_AT_LEAST(20101106,0) |
| dconf = |
| ap_get_module_config(r->per_dir_config, &proxy_module); |
| if (ap_proxy_should_override(dconf, r->status)) { |
| #else |
| if (ap_proxy_should_override(conf, r->status)) { |
| #endif |
| int status = r->status; |
| r->status = HTTP_OK; |
| r->status_line = NULL; |
| |
| apr_brigade_cleanup(bb); |
| apr_brigade_cleanup(pass_bb); |
| |
| return status; |
| } |
| |
| while (!finish) { |
| rv = ap_get_brigade(rp->input_filters, bb, |
| AP_MODE_READBYTES, mode, conf->io_buffer_size); |
| if (APR_STATUS_IS_EAGAIN(rv) |
| || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))) { |
| e = apr_bucket_flush_create(c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| if (ap_pass_brigade(r->output_filters, bb) || c->aborted) { |
| break; |
| } |
| apr_brigade_cleanup(bb); |
| mode = APR_BLOCK_READ; |
| continue; |
| } |
| else if (rv == APR_EOF) { |
| break; |
| } |
| else if (rv != APR_SUCCESS) { |
| ap_proxy_backend_broke(r, bb); |
| ap_pass_brigade(r->output_filters, bb); |
| backend_broke = 1; |
| break; |
| } |
| |
| mode = APR_NONBLOCK_READ; |
| apr_brigade_length(bb, 0, &readbytes); |
| backend->worker->s->read += readbytes; |
| |
| if (APR_BRIGADE_EMPTY(bb)) { |
| apr_brigade_cleanup(bb); |
| break; |
| } |
| |
| ap_proxy_buckets_lifetime_transform(r, bb, pass_bb); |
| |
| /* found the last brigade? */ |
| if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) |
| finish = 1; |
| |
| /* do not pass chunk if it is zero_sized */ |
| apr_brigade_length(pass_bb, 0, &readbytes); |
| |
| if ((readbytes > 0 |
| && ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS) |
| || c->aborted) { |
| finish = 1; |
| } |
| |
| apr_brigade_cleanup(bb); |
| apr_brigade_cleanup(pass_bb); |
| } |
| |
| e = apr_bucket_eos_create(c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| ap_pass_brigade(r->output_filters, bb); |
| |
| apr_brigade_cleanup(bb); |
| |
| if (c->aborted || backend_broke) { |
| return DONE; |
| } |
| |
| return OK; |
| } |
| |
| static int uwsgi_handler(request_rec *r, proxy_worker * worker, |
| proxy_server_conf * conf, char *url, |
| const char *proxyname, apr_port_t proxyport) |
| { |
| int status; |
| proxy_conn_rec *backend = NULL; |
| apr_pool_t *p = r->pool; |
| char server_portstr[32]; |
| char *u_path_info; |
| apr_uri_t *uri; |
| |
| if (ap_cstr_casecmpn(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "declining URL %s", url); |
| return DECLINED; |
| } |
| |
| uri = apr_palloc(r->pool, sizeof(*uri)); |
| |
| /* ADD PATH_INFO (unescaped) */ |
| u_path_info = ap_strchr(url + sizeof(UWSGI_SCHEME) + 2, '/'); |
| if (!u_path_info) { |
| u_path_info = apr_pstrdup(r->pool, "/"); |
| } |
| else if (ap_unescape_url(u_path_info) != OK) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10100) |
| "unable to decode uwsgi uri: %s", url); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| else { |
| /* Remove duplicate slashes at the beginning of PATH_INFO */ |
| while (u_path_info[1] == '/') { |
| u_path_info++; |
| } |
| } |
| apr_table_add(r->subprocess_env, "PATH_INFO", u_path_info); |
| |
| /* Create space for state information */ |
| status = ap_proxy_acquire_connection(UWSGI_SCHEME, &backend, worker, |
| r->server); |
| if (status != OK) { |
| goto cleanup; |
| } |
| backend->is_ssl = 0; |
| |
| /* Step One: Determine Who To Connect To */ |
| status = ap_proxy_determine_connection(p, r, conf, worker, backend, |
| uri, &url, proxyname, proxyport, |
| server_portstr, |
| sizeof(server_portstr)); |
| if (status != OK) { |
| goto cleanup; |
| } |
| |
| |
| /* Step Two: Make the Connection */ |
| if (ap_proxy_connect_backend(UWSGI_SCHEME, backend, worker, r->server)) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10101) |
| "failed to make connection to backend: %s:%u", |
| backend->hostname, backend->port); |
| status = HTTP_SERVICE_UNAVAILABLE; |
| goto cleanup; |
| } |
| |
| /* Step Three: Create conn_rec */ |
| if ((status = ap_proxy_connection_create(UWSGI_SCHEME, backend, |
| r->connection, |
| r->server)) != OK) |
| goto cleanup; |
| |
| /* Step Four: Process the Request */ |
| if (((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) |
| || ((status = uwsgi_send_headers(r, backend)) != OK) |
| || ((status = uwsgi_send_body(r, backend)) != OK) |
| || ((status = uwsgi_response(r, backend, conf)) != OK)) { |
| goto cleanup; |
| } |
| |
| cleanup: |
| if (backend) { |
| backend->close = 1; /* always close the socket */ |
| ap_proxy_release_connection(UWSGI_SCHEME, backend, r->server); |
| } |
| return status; |
| } |
| |
| |
| static void register_hooks(apr_pool_t * p) |
| { |
| proxy_hook_scheme_handler(uwsgi_handler, NULL, NULL, APR_HOOK_FIRST); |
| proxy_hook_canon_handler(uwsgi_canon, NULL, NULL, APR_HOOK_FIRST); |
| } |
| |
| |
| module AP_MODULE_DECLARE_DATA proxy_uwsgi_module = { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* create per-directory config structure */ |
| NULL, /* merge per-directory config structures */ |
| NULL, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| NULL, /* command table */ |
| register_hooks /* register hooks */ |
| }; |