| /* ==================================================================== |
| * 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. |
| * ==================================================================== |
| */ |
| |
| #include <stdlib.h> |
| |
| #include <apr.h> |
| #include <apr_uri.h> |
| #include <apr_signal.h> |
| #include <apr_strings.h> |
| #include <apr_atomic.h> |
| #include <apr_base64.h> |
| #include <apr_getopt.h> |
| #include <apr_version.h> |
| |
| #include "serf.h" |
| |
| typedef struct app_ctx_t { |
| int foo; |
| apr_pool_t *pool; |
| } app_ctx_t; |
| |
| typedef struct listener_ctx_t { |
| app_ctx_t *app; |
| const char *proto; |
| |
| } listener_ctx_t; |
| |
| typedef struct client_ctx_t { |
| listener_ctx_t *listener; |
| apr_pool_t *pool; |
| serf_bucket_alloc_t *alloc; |
| } client_ctx_t; |
| |
| typedef struct request_ctx_t { |
| client_ctx_t *client; |
| apr_pool_t *pool; |
| serf_bucket_t *headers; |
| const char *method; |
| const char *path; |
| int http_version; |
| } request_ctx_t; |
| |
| static int pool_abort_func(int retcode) |
| { |
| fprintf(stderr, "Out of memory\n"); |
| abort(); |
| return 0; |
| } |
| |
| /* A flag to see if we've been cancelled by the client or not. */ |
| static volatile sig_atomic_t cancelled = FALSE; |
| |
| /* A signal handler to support cancellation. */ |
| static void |
| signal_handler(int signum) |
| { |
| apr_signal(signum, SIG_IGN); |
| cancelled = TRUE; |
| } |
| |
| |
| static apr_status_t client_setup(apr_socket_t *skt, |
| serf_bucket_t **read_bkt, |
| serf_bucket_t **write_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| client_ctx_t *cctx = setup_baton; |
| if (cctx->alloc == NULL) |
| cctx->alloc = serf_bucket_allocator_create(cctx->pool, NULL, NULL); |
| |
| *read_bkt = serf_bucket_socket_create(skt, cctx->alloc); |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t client_closed(serf_incoming_t *incoming, |
| void *closed_baton, |
| apr_status_t why, |
| apr_pool_t *pool) |
| { |
| client_ctx_t *cctx = closed_baton; |
| apr_pool_destroy(cctx->pool); |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t request_handler(serf_incoming_request_t *req, |
| serf_bucket_t *request, |
| void *handler_baton, |
| apr_pool_t *pool) |
| { |
| request_ctx_t *rctx = handler_baton; |
| apr_status_t status; |
| |
| if (!rctx->method) { |
| status = serf_bucket_incoming_request_read(&rctx->headers, |
| &rctx->method, |
| &rctx->path, |
| &rctx->http_version, |
| request); |
| if (status) { |
| rctx->method = NULL; |
| return status; |
| } |
| } |
| |
| status = serf_bucket_incoming_request_wait_for_headers(request); |
| if (status) |
| return status; |
| |
| status = serf_incoming_response_create(req); |
| if (status) |
| return status; |
| |
| do |
| { |
| const char *data; |
| apr_size_t len; |
| |
| status = serf_bucket_read(request, SERF_READ_ALL_AVAIL, &data, &len); |
| } while (status == APR_SUCCESS); |
| |
| return status; |
| } |
| |
| static apr_status_t request_generate_response(serf_bucket_t **resp_bkt, |
| serf_incoming_request_t *req, |
| void *setup_baton, |
| serf_bucket_alloc_t *alloc, |
| apr_pool_t *pool) |
| { |
| request_ctx_t *rctx = setup_baton; |
| serf_bucket_t *agg = serf_bucket_aggregate_create(alloc); |
| serf_bucket_t *body; |
| serf_bucket_t *tmp; |
| #define CRLF "\r\n" |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING("HTTP/1.1 200 BOE" CRLF, alloc); |
| serf_bucket_aggregate_append(agg, tmp); |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING("Content-Type: text/plain" CRLF, alloc); |
| serf_bucket_aggregate_append(agg, tmp); |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING(CRLF, alloc); |
| serf_bucket_aggregate_append(agg, tmp); |
| |
| body = serf_bucket_aggregate_create(alloc); |
| |
| if (rctx->method) |
| { |
| tmp = SERF_BUCKET_SIMPLE_STRING("Method: ", alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| |
| tmp = serf_bucket_simple_copy_create(rctx->method, strlen(rctx->method), alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING(CRLF "Path: ", alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| |
| tmp = serf_bucket_simple_copy_create(rctx->path, strlen(rctx->path), alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING(CRLF, alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| } |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING(CRLF, alloc); |
| serf_bucket_aggregate_append(body, tmp); |
| |
| tmp = body; |
| serf_bucket_aggregate_append(agg, tmp); |
| |
| tmp = SERF_BUCKET_SIMPLE_STRING(CRLF, alloc); |
| serf_bucket_aggregate_append(agg, tmp); |
| |
| *resp_bkt = agg; |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t client_request_acceptor(serf_bucket_t **req_bkt, |
| serf_bucket_t *stream, |
| serf_incoming_request_t *req, |
| void *request_baton, |
| serf_incoming_request_handler_t *handler, |
| void **handler_baton, |
| serf_incoming_response_setup_t *response_setup, |
| void **response_setup_baton, |
| apr_pool_t *pool) |
| { |
| client_ctx_t *cctx = request_baton; |
| apr_pool_t *request_pool; |
| request_ctx_t *rctx; |
| |
| *req_bkt = serf_bucket_incoming_request_create(stream, stream->allocator); |
| |
| apr_pool_create(&request_pool, cctx->pool); |
| |
| rctx = apr_pcalloc(request_pool, sizeof(*rctx)); |
| rctx->client = cctx; |
| |
| *handler = request_handler; |
| *handler_baton = rctx; |
| |
| *response_setup = request_generate_response; |
| *response_setup_baton = rctx; |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| static apr_status_t client_accept(serf_context_t *ctx, |
| serf_listener_t *l, |
| void *accept_baton, |
| apr_socket_t *insock, |
| apr_pool_t *pool) |
| { |
| serf_incoming_t *client; |
| listener_ctx_t *lctx = accept_baton; |
| apr_pool_t *cctx_pool; |
| client_ctx_t *cctx; |
| apr_status_t status; |
| |
| apr_pool_create(&cctx_pool, pool); |
| cctx = apr_pcalloc(cctx_pool, sizeof(*cctx)); |
| cctx->pool = cctx_pool; |
| cctx->listener = lctx; |
| |
| status = serf_incoming_create2(&client, ctx, insock, |
| client_setup, cctx, |
| client_closed, cctx, |
| client_request_acceptor, cctx, |
| pool); |
| if (status) |
| return status; |
| |
| if (lctx->proto && !strcmp(lctx->proto, "fcgi")) { |
| serf_incoming_set_framing_type(client, |
| SERF_CONNECTION_FRAMING_TYPE_FCGI); |
| } |
| else if (lctx->proto && !strcmp(lctx->proto, "h2c")) { |
| serf_incoming_set_framing_type(client, |
| SERF_CONNECTION_FRAMING_TYPE_HTTP2); |
| } |
| else if (lctx->proto && !strcmp(lctx->proto, "http1")) { |
| serf_incoming_set_framing_type(client, |
| SERF_CONNECTION_FRAMING_TYPE_HTTP1); |
| } |
| |
| return status; |
| } |
| |
| |
| /* Value for 'no short code' should be > 255 */ |
| #define CERTFILE 256 |
| #define CERTPWD 257 |
| #define HTTP2FLAG 258 |
| #define H2DIRECT 259 |
| |
| static const apr_getopt_option_t options[] = |
| { |
| |
| { "help", 'h', 0, "Display this help" }, |
| { "version", 'V', 0, "Display version" }, |
| { "listen", 'l', 1, "<[protocol,][host:]port> Configure listener"}, |
| { "verbose", 'v', 0, "Enable debug logging" }, |
| /* { NULL, 'H', 0, "Print response headers" }, |
| { NULL, 'n', 1, "<count> Fetch URL <count> times" }, |
| { NULL, 'c', 1, "<count> Use <count> concurrent connections" }, |
| { NULL, 'x', 1, "<count> Number of maximum outstanding requests inflight" }, |
| { "user", 'U', 1, "<user> Username for Basic/Digest authentication" }, |
| { "pwd", 'P', 1, "<password> Password for Basic/Digest authentication" }, |
| { NULL, 'm', 1, "<method> Use the <method> HTTP Method" }, |
| { NULL, 'f', 1, "<file> Use the <file> as the request body" }, |
| { NULL, 'p', 1, "<hostname:port> Use the <host:port> as proxy server" }, |
| { "cert", CERTFILE, 1, "<file> Use SSL client certificate <file>" }, |
| { "certpwd", CERTPWD, 1, "<password> Password for the SSL client certificate" }, |
| { NULL, 'r', 1, "<header:value> Use <header:value> as request header" }, |
| { "http2", HTTP2FLAG, 0, "Enable http2 (https only) (Experimental)" }, |
| { "h2direct",H2DIRECT, 0, "Enable h2direct (Experimental)" },*/ |
| |
| { NULL, 0 } |
| }; |
| |
| static void print_usage(apr_pool_t *pool) |
| { |
| int i = 0; |
| |
| puts("serf_httpd [options] directory\n"); |
| puts("Options:"); |
| |
| while (options[i].optch > 0) { |
| const apr_getopt_option_t* o = &options[i]; |
| |
| if (o->optch <= 255) { |
| printf(" -%c", o->optch); |
| if (o->name) |
| printf(", "); |
| } |
| else { |
| printf(" "); |
| } |
| |
| printf("%s%s\t%s\n", |
| o->name ? "--" : "\t", |
| o->name ? o->name : "", |
| o->description); |
| |
| i++; |
| } |
| } |
| |
| static void configure_listener(serf_context_t *ctx, |
| app_ctx_t *app, |
| const char *arg, |
| apr_pool_t *pool) |
| { |
| const char *listen_spec; |
| char *addr = NULL; |
| char *scope_id = NULL; |
| apr_port_t port; |
| apr_status_t status; |
| listener_ctx_t *lctx; |
| serf_listener_t *listener; |
| |
| char *comma = strchr(arg, ','); |
| |
| listen_spec = comma ? comma + 1 : arg; |
| |
| status = apr_parse_addr_port(&addr, &scope_id, &port, listen_spec, pool); |
| if (status) { |
| printf("Error parsing listen address: %s\n", listen_spec); |
| exit(1); |
| } |
| |
| if (!addr) { |
| addr = "0.0.0.0"; |
| } |
| |
| if (port == 0) { |
| port = 8080; |
| } |
| |
| lctx = apr_pcalloc(pool, sizeof(*lctx)); |
| lctx->app = app; |
| |
| if (comma) |
| lctx->proto = apr_pstrmemdup(pool, arg, comma - arg); |
| |
| status = serf_listener_create(&listener, ctx, addr, port, lctx, |
| client_accept, pool); |
| if (status) { |
| printf("Error creating listener '%s': %d", listen_spec, status); |
| exit(1); |
| } |
| } |
| |
| int main(int argc, const char **argv) |
| { |
| apr_status_t status; |
| apr_pool_t *app_pool; |
| apr_pool_t *scratch_pool; |
| serf_context_t *context; |
| app_ctx_t app_ctx; |
| apr_getopt_t *opt; |
| apr_allocator_t *allocator; |
| int opt_c; |
| int verbose = 0; |
| const char *opt_arg; |
| const char *root_dir; |
| |
| apr_initialize(); |
| atexit(apr_terminate); |
| |
| apr_pool_create(&app_pool, NULL); |
| |
| apr_pool_abort_set(pool_abort_func, app_pool); |
| |
| /* Keep a maximum of 16 MB unused memory inside APR. */ |
| allocator = apr_pool_allocator_get(app_pool); |
| if (allocator != NULL) |
| apr_allocator_max_free_set(allocator, 16384 * 1024); |
| /* else: APR pool debugging... leave this to apr */ |
| |
| apr_signal(SIGINT, signal_handler); |
| #ifdef SIGBREAK |
| /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ |
| apr_signal(SIGBREAK, signal_handler); |
| #endif |
| #ifdef SIGHUP |
| apr_signal(SIGHUP, signal_handler); |
| #endif |
| #ifdef SIGTERM |
| apr_signal(SIGTERM, signal_handler); |
| #endif |
| |
| #ifdef SIGPIPE |
| /* Disable SIGPIPE generation for the platforms that have it. */ |
| apr_signal(SIGPIPE, SIG_IGN); |
| #endif |
| |
| |
| apr_pool_create(&scratch_pool, app_pool); |
| |
| apr_getopt_init(&opt, scratch_pool, argc, argv); |
| context = serf_context_create(app_pool); |
| |
| apr_getopt_init(&opt, scratch_pool, argc, argv); |
| while ((status = apr_getopt_long(opt, options, &opt_c, &opt_arg)) == |
| APR_SUCCESS) { |
| switch (opt_c) { |
| case 'h': |
| print_usage(scratch_pool); |
| exit(0); |
| break; |
| case 'V': |
| puts("Serf version: " SERF_VERSION_STRING); |
| exit(0); |
| case 'l': |
| configure_listener(context, &app_ctx, opt_arg, app_pool); |
| break; |
| case 'v': |
| verbose++; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (opt->ind != opt->argc - 1) { |
| print_usage(scratch_pool); |
| exit(-1); |
| } |
| |
| root_dir = argv[opt->ind]; |
| |
| /* Setup debug logging */ |
| if (verbose) { |
| serf_log_output_t *output; |
| apr_status_t status; |
| apr_uint32_t level; |
| |
| level = SERF_LOG_WARNING; |
| if (verbose >= 3) |
| level = SERF_LOG_DEBUG; |
| else if (verbose >= 2) |
| level = SERF_LOG_INFO; |
| |
| status = serf_logging_create_stream_output( |
| &output, |
| context, |
| level, |
| SERF_LOGCOMP_ALL_MSG & ~(SERF_LOGCOMP_RAWMSG | SERF_LOGCOMP_SSLMSG), |
| SERF_LOG_DEFAULT_LAYOUT, |
| stderr, |
| app_pool); |
| |
| if (!status) |
| serf_logging_add_output(context, output); |
| } |
| |
| while (1) { |
| apr_pool_clear(scratch_pool); |
| status = serf_context_run(context, apr_time_from_msec(50), |
| scratch_pool); |
| if (APR_STATUS_IS_TIMEUP(status)) { |
| if (cancelled) |
| break; |
| else |
| continue; |
| } |
| if (status) { |
| char buf[200]; |
| const char *err_string; |
| err_string = serf_error_string(status); |
| if (!err_string) { |
| err_string = apr_strerror(status, buf, sizeof(buf)); |
| } |
| |
| printf("Error running context: (%d) %s\n", status, err_string); |
| apr_pool_destroy(app_pool); |
| exit(1); |
| } |
| } |
| |
| apr_pool_destroy(app_pool); |
| return 0; |
| } |