static void accept_connection(nxe_loop* loop, nxweb_http_server_listening_socket* lsock) { //static int next_net_thread_idx=0; //nxweb_accept accept_msg; int client_fd; struct sockaddr_in client_addr; socklen_t client_len=sizeof(client_addr); nxe_unset_timer(loop, NXWEB_TIMER_ACCEPT_RETRY, &lsock->accept_retry_timer); while (!shutdown_in_progress) { client_fd=accept4(lsock->listen_source.fd, (struct sockaddr *)&client_addr, &client_len, SOCK_NONBLOCK); if (client_fd!=-1) { if (/*_nxweb_set_non_block(client_fd) ||*/ _nxweb_setup_client_socket(client_fd)) { _nxweb_close_bad_socket(client_fd); nxweb_log_error("failed to setup client socket"); continue; } int lconf_idx=lsock->idx; nxweb_net_thread_data* tdata=(nxweb_net_thread_data*)((char*)(lsock-lconf_idx)-offsetof(nxweb_net_thread_data, listening_sock)); nxweb_http_server_connection* conn=nxp_alloc(tdata->free_conn_pool); nxweb_http_server_connection_init(conn, tdata, lconf_idx); inet_ntop(AF_INET, &client_addr.sin_addr, conn->remote_addr, sizeof(conn->remote_addr)); nxweb_http_server_connection_connect(conn, loop, client_fd); } else { if (errno!=EAGAIN) { nxweb_log_error("accept() failed %d", errno); // retry accept after timeout nxe_set_timer(loop, NXWEB_TIMER_ACCEPT_RETRY, &lsock->accept_retry_timer); } break; } } }
static void on_shutdown() { if (!py_module) return; // not initialized // shut down the interpreter nxweb_log_error("shutting down python"); PyEval_AcquireLock(); PyThreadState_Swap(py_main_thread_state); Py_XDECREF(py_nxweb_on_request_func); Py_XDECREF(py_module); Py_Finalize(); nxweb_log_error("python finalized"); }
static void on_net_thread_diagnostics(nxe_subscriber* sub, nxe_publisher* pub, nxe_data data) { nxweb_log_error("net thread diagnostics begin"); nxweb_module* mod=nxweb_server_config.module_list; while (mod) { if (mod->on_thread_diagnostics) mod->on_thread_diagnostics(); mod=mod->next; } nxweb_log_error("net thread diagnostics end"); }
static void on_net_thread_shutdown(nxe_subscriber* sub, nxe_publisher* pub, nxe_data data) { int i; nxweb_net_thread_data* tdata=(nxweb_net_thread_data*)((char*)sub-offsetof(nxweb_net_thread_data, shutdown_sub)); //nxe_loop* loop=sub->super.loop; nxweb_log_error("shutting down net thread"); nxe_unsubscribe(pub, sub); nxweb_server_listen_config* lconf; nxweb_http_server_listening_socket* lsock; for (i=0, lconf=nxweb_server_config.listen_config, lsock=tdata->listening_sock; i<NXWEB_MAX_LISTEN_SOCKETS; i++, lconf++, lsock++) { if (lconf->listen_fd) { nxe_unsubscribe(&lsock->listen_source.data_notify, &lsock->listen_sub); nxe_unregister_listenfd_source(&lsock->listen_source); } } nxe_unregister_eventfd_source(&tdata->shutdown_efs); nxe_finalize_eventfd_source(&tdata->shutdown_efs); nxe_unregister_eventfd_source(&tdata->diagnostics_efs); nxe_finalize_eventfd_source(&tdata->diagnostics_efs); nxw_finalize_factory(&tdata->workers_factory); // close keep-alive connections to backends for (i=0; i<NXWEB_MAX_PROXY_POOLS; i++) { nxd_http_proxy_pool_finalize(&tdata->proxy_pool[i]); } nxweb_access_log_thread_flush(); }
int nxweb_setup_http_proxy_pool(int idx, const char* host_and_port) { assert(idx>=0 && idx<NXWEB_MAX_PROXY_POOLS); nxweb_log_error("proxy backend #%d: %s", idx, host_and_port); nxweb_server_config.http_proxy_pool_config[idx].host=host_and_port; nxweb_server_config.http_proxy_pool_config[idx].saddr=_nxweb_resolve_host(host_and_port, 0); return !!nxweb_server_config.http_proxy_pool_config[idx].saddr; }
void* nxp_alloc(nxp_pool* pool) { nxp_object* obj=pool->free_first; if (!obj) { int nitems=pool->chunk->nitems*2; if (nitems>1024) nitems=1024; nxp_chunk* chunk=nx_alloc(offsetof(nxp_chunk, pool)+nitems*pool->object_size); if (!chunk) { nxweb_log_error("nx_pool: alloc obj[%d] chunk failed", nitems); return 0; } chunk->nitems=nitems; chunk->prev=pool->chunk; chunk->id=chunk->prev? chunk->prev->id+1 : 1; pool->chunk=chunk; nxp_init_chunk(pool); obj=pool->free_first; } if (obj->next) { pool->free_first=obj->next; obj->next->prev=0; } else { pool->free_first= pool->free_last=0; } obj->in_use=1; obj->prev=0; obj->next=0; return obj+1; }
static int on_startup() { struct stat fi; if (!project_app || !*project_app) { nxweb_log_error("python wsgi app not specified; skipping python initialization"); return 0; } static const char* prog_name="python/nxwebpy.py"; if (stat(prog_name, &fi)==-1) { #ifdef NXWEB_LIBDIR prog_name=NXWEB_LIBDIR "/nxwebpy.py"; if (stat(prog_name, &fi)==-1) { #endif nxweb_log_error("%s is missing; skipping python initialization", prog_name); return 0; #ifdef NXWEB_LIBDIR } #endif } Py_SetProgramName((char*)prog_name); // initialize thread support PyEval_InitThreads(); Py_Initialize(); char *a[]={(char*)prog_name, (char*)project_root, (char*)project_app, (char*)virtualenv_path}; PySys_SetArgv(4, a); PyObject* py_module_name=PyString_FromString(MODULE_NAME); assert(py_module_name); // save a pointer to the main PyThreadState object py_main_thread_state=PyThreadState_Get(); py_module=PyImport_Import(py_module_name); if (!py_module || !PyModule_Check(py_module)) { fprintf(stderr, "can't load python module %s; check parse errors:\n", MODULE_NAME); PyErr_Print(); exit(0); } Py_DECREF(py_module_name); py_nxweb_on_request_func=PyObject_GetAttrString(py_module, FUNC_NAME); assert(py_nxweb_on_request_func && PyCallable_Check(py_nxweb_on_request_func)); // release the lock PyEval_ReleaseLock(); return 0; }
static void on_listen_event(nxe_subscriber* sub, nxe_publisher* pub, nxe_data data) { if (data.i) { nxweb_log_error("listening socket error %d", data.i); return; } nxweb_http_server_listening_socket* lsock=OBJ_PTR_FROM_FLD_PTR(nxweb_http_server_listening_socket, listen_sub, sub); accept_connection(sub->super.loop, lsock); }
void _nxweb_launch_diagnostics() { nxweb_log_error("server diagnostics begin"); nxweb_module* mod=nxweb_server_config.module_list; while (mod) { if (mod->on_server_diagnostics) mod->on_server_diagnostics(); mod=mod->next; } nxweb_log_error("server diagnostics end"); int i; nxweb_net_thread_data* tdata; for (i=0, tdata=_nxweb_net_threads; i<_nxweb_num_net_threads; i++, tdata++) { nxe_trigger_eventfd(&tdata->diagnostics_efs); } }
static void invoke_request_handler_in_worker(void* ptr) { nxweb_http_server_connection* conn=ptr; if (conn && conn->handler && conn->handler->on_request) { conn->handler->on_request(conn, &conn->hsp.req, &conn->hsp._resp); nxd_http_server_proto_finish_response(&conn->hsp._resp); } else { nxweb_log_error("invalid conn handler reached worker"); } }
static int tmpl_load(nxt_context* ctx, const char* uri, nxt_file* dst_file, nxt_block* dst_block) { // function to make subrequests if (dst_block) { nxweb_log_error("including file %s", uri); nxt_block_append_value(ctx, dst_block, "{% This is included file %}", sizeof("{% This is included file %}")-1, 0); } else { nxweb_log_error("loading template from %s", uri); if (!strcmp(uri, "base")) { const char* tmpl_src=" {%block _top_%}{%raw%}{{{{%%%%}}}}{%endraw%}{% include aaa %}AAA-YYY{%block header%}Header{%endblock%} bye...{% endblock %}{% block title %}New Title{% endblock %}"; char* tmpl=nxb_copy_obj(ctx->nxb, tmpl_src, strlen(tmpl_src)+1); nxt_parse_file(dst_file, (char*)tmpl, strlen(tmpl)); } else if (!strcmp(uri, "ttt")) { const char* tmpl_src=" {% extends 'base'%} {%block header%}Bbbb {%block title%}{%endblock%} {% parent %}{%endblock%}"; char* tmpl=nxb_copy_obj(ctx->nxb, tmpl_src, strlen(tmpl_src)+1); nxt_parse_file(dst_file, (char*)tmpl, strlen(tmpl)); } } }
static nxweb_result python_on_post_data(nxweb_http_server_connection* conn, nxweb_http_request* req, nxweb_http_response* resp) { if (req->content_length>0 && req->content_length<NXWEB_MAX_REQUEST_BODY_SIZE) { // fallback to default in-memory buffering return NXWEB_NEXT; } if (!conn->handler->dir) { nxweb_log_warning("python handler temp upload file dir not set => skipping file buffering for %s", req->uri); return NXWEB_NEXT; } nxe_ssize_t upload_size_limit=conn->handler->size? conn->handler->size : NXWEB_MAX_PYTHON_UPLOAD_SIZE; if (req->content_length > upload_size_limit) { nxweb_send_http_error(resp, 413, "Request Entity Too Large"); resp->keep_alive=0; // close connection nxweb_start_sending_response(conn, resp); return NXWEB_OK; } char* fname_template=nxb_alloc_obj(req->nxb, strlen(conn->handler->dir)+sizeof("/py_upload_tmp_XXXXXX")+1); strcat(strcpy(fname_template, conn->handler->dir), "/py_upload_tmp_XXXXXX"); if (nxweb_mkpath(fname_template, 0755)==-1) { nxweb_log_error("can't create path to temp upload file %s; check permissions", fname_template); nxweb_send_http_error(resp, 500, "Internal Server Error"); resp->keep_alive=0; // close connection nxweb_start_sending_response(conn, resp); return NXWEB_OK; } int fd=mkstemp(fname_template); if (fd==-1) { nxweb_log_error("can't open (mkstemp()) temp upload file for %s", req->uri); nxweb_send_http_error(resp, 500, "Internal Server Error"); resp->keep_alive=0; // close connection nxweb_start_sending_response(conn, resp); return NXWEB_OK; } unlink(fname_template); // auto-delete on close() nxd_fwbuffer* fwb=nxb_alloc_obj(req->nxb, sizeof(nxd_fwbuffer)); nxweb_set_request_data(req, PYTHON_HANDLER_KEY, (nxe_data)(void*)fwb, python_request_data_finalize); nxd_fwbuffer_init(fwb, fd, upload_size_limit); conn->hsp.cls->connect_request_body_out(&conn->hsp, &fwb->data_in); conn->hsp.cls->start_receiving_request_body(&conn->hsp); return NXWEB_OK; }
nxweb_result nxweb_cache_try(nxweb_http_server_connection* conn, nxweb_http_response* resp, const char* key, time_t if_modified_since, time_t revalidated_mtime) { if (*key==' ' || *key=='*') return NXWEB_MISS; // not implemented yet nxe_time_t loop_time=nxweb_get_loop_time(conn); ah_iter_t ci; //nxweb_log_error("trying cache for %s", fpath); pthread_mutex_lock(&_nxweb_cache_mutex); if ((ci=alignhash_get(nxweb_cache, _nxweb_cache, key))!=alignhash_end(_nxweb_cache)) { nxweb_cache_rec* rec=alignhash_value(_nxweb_cache, ci); if (rec->last_modified==revalidated_mtime) { rec->expires_time=loop_time+NXWEB_DEFAULT_CACHED_TIME; nxweb_log_info("revalidated %s in memcache", key); } if (loop_time <= rec->expires_time) { if (rec!=_nxweb_cache_head) { cache_rec_unlink(rec); cache_rec_link(rec); // relink to head } if (if_modified_since && rec->last_modified<=if_modified_since) { pthread_mutex_unlock(&_nxweb_cache_mutex); resp->status_code=304; resp->status="Not Modified"; return NXWEB_OK; } rec->ref_count++; // this must be within mutex-protected section pthread_mutex_unlock(&_nxweb_cache_mutex); resp->content_length=rec->content_length; resp->content=rec->content; resp->content_type=rec->content_type; resp->content_charset=rec->content_charset; resp->last_modified=rec->last_modified; resp->gzip_encoded=rec->gzip_encoded; conn->hsp.req_data=rec; conn->hsp.req_finalize=cache_rec_unref; return NXWEB_OK; } else if (!revalidated_mtime) { pthread_mutex_unlock(&_nxweb_cache_mutex); return NXWEB_REVALIDATE; } alignhash_del(nxweb_cache, _nxweb_cache, ci); cache_rec_unlink(rec); if (!rec->ref_count) { nx_free(rec); } else { // this is normal; just notification nxweb_log_error("removed %s [%p] from cache while its ref_count=%d", key, rec, rec->ref_count); } } pthread_mutex_unlock(&_nxweb_cache_mutex); return NXWEB_MISS; }
static void cache_finalize() { ah_iter_t ci; for (ci=alignhash_begin(_nxweb_cache); ci!=alignhash_end(_nxweb_cache); ci++) { if (alignhash_exist(_nxweb_cache, ci)) { nxweb_cache_rec* rec=alignhash_value(_nxweb_cache, ci); if (rec->ref_count) nxweb_log_error("file %s still in cache with ref_count=%d", alignhash_key(_nxweb_cache, ci), rec->ref_count); cache_rec_unlink(rec); nx_free(rec); } } alignhash_destroy(nxweb_cache, _nxweb_cache); pthread_mutex_destroy(&_nxweb_cache_mutex); }
void _nxweb_register_handler(nxweb_handler* handler, nxweb_handler* base) { handler->prefix_len=handler->prefix? strlen(handler->prefix) : 0; if (handler->prefix_len) { if (handler->prefix[0]!='/') { nxweb_log_error("handler's prefix must start with '/'; handler=%s with prefix=%s not allowed", handler->name, handler->prefix); exit(0); } } handler->vhost_len=handler->vhost? strlen(handler->vhost) : 0; if (handler->dir && strstr(handler->dir, "{host}")) handler->flags|=_NXWEB_HOST_DEPENDENT_DIR; else handler->flags&=~_NXWEB_HOST_DEPENDENT_DIR; if (base) { if (!handler->on_generate_cache_key) handler->on_generate_cache_key=base->on_generate_cache_key; if (!handler->on_select) handler->on_select=base->on_select; if (!handler->on_headers) handler->on_headers=base->on_headers; if (!handler->on_post_data) handler->on_post_data=base->on_post_data; if (!handler->on_post_data_complete) handler->on_post_data_complete=base->on_post_data_complete; if (!handler->on_request) handler->on_request=base->on_request; if (!handler->on_complete) handler->on_complete=base->on_complete; if (!handler->on_error) handler->on_error=base->on_error; if (!handler->flags) handler->flags=base->flags; } int i; nxweb_filter* filter; for (i=0; i<NXWEB_MAX_FILTERS; i++) { filter=handler->filters[i]; if (!filter) break; } handler->num_filters=i; if (!nxweb_server_config.handler_list) { nxweb_server_config.handler_list=handler; handler->next=0; return; } else { nxweb_handler* h=nxweb_server_config.handler_list; if (handler->priority < h->priority) { handler->next=h; nxweb_server_config.handler_list=handler; } else { while (h->next && h->next->priority <= handler->priority) { h=h->next; } handler->next=h->next; h->next=handler; } } }
nxweb_result _nxweb_default_request_dispatcher(nxweb_http_server_connection* conn, nxweb_http_request* req, nxweb_http_response* resp) { nxweb_handler* h=nxweb_server_config.handler_list; const char* uri=req->uri; const char* host=req->host; _Bool secure=conn->secure; // NOTE: host is always lowercase; ensured by _nxweb_parse_http_request() int host_len; if (host) { const char* p=strchr(host, ':'); host_len=p? p-host : strlen(host); } else { host_len=0; } int uri_len=strlen(uri); while (h) { if ((secure && !h->insecure_only) || (!secure && !h->secure_only)) { if (is_method_allowed(req, h->flags)) { if (!h->vhost_len || (host_len && nxweb_vhost_match(host, host_len, h->vhost, h->vhost_len))) { if (!h->prefix_len || nxweb_url_prefix_match(uri, uri_len, h->prefix, h->prefix_len)) { nxweb_result res=nxweb_select_handler(conn, req, resp, h, h->param); if (res!=NXWEB_NEXT) { if (res==NXWEB_ERROR) { // request processing terminated by http error response if (req->content_length) resp->keep_alive=0; // close connection if there is body pending nxweb_start_sending_response(conn, resp); return NXWEB_ERROR; } if (res!=NXWEB_OK) { nxweb_log_error("handler %s on_select() returned error %d", h->name, res); break; } return NXWEB_OK; } } } } } h=h->next; } req->path_info=0; nxweb_select_handler(conn, req, resp, &nxweb_default_handler, (nxe_data)0); return NXWEB_OK; }
static void on_config(const nx_json* js) { if (nxweb_main_args.python_root) project_root=nxweb_main_args.python_root; else if (js) { const char* v=nx_json_get(js, "project_path")->text_value; if (v) project_root=v; } if (nxweb_main_args.python_wsgi_app) project_app=nxweb_main_args.python_wsgi_app; else if (js) { const char* v=nx_json_get(js, "wsgi_application")->text_value; if (v) project_app=v; } if (nxweb_main_args.python_virtualenv_path) virtualenv_path=nxweb_main_args.python_virtualenv_path; else if (js) { const char* v=nx_json_get(js, "virtualenv_path")->text_value; if (v) virtualenv_path=v; } nxweb_log_error("python config: root=%s, app=%s, virtualenv=%s", project_root, project_app, virtualenv_path); }
static void timer_backend_on_timeout(nxe_timer* timer, nxe_data data) { nxweb_log_debug("timer_backend_on_timeout"); nxweb_http_proxy_request_data* rdata=(nxweb_http_proxy_request_data*)((char*)timer-offsetof(nxweb_http_proxy_request_data, timer_backend)); nxweb_http_server_connection* conn=rdata->conn; if (rdata->hpx->hcp.req_body_sending_started || rdata->response_sending_started) { // backend did respond in time (headers or 100-continue) but request is not done yet // do nothing, continue processing until parent connection times out nxweb_log_warning("backend connection %p timeout; backend responded; post=%d resp=%d", conn, (int)rdata->hpx->hcp.req_body_sending_started, (int)rdata->response_sending_started); } else if (rdata->retry_count>=NXWEB_PROXY_RETRY_COUNT) { nxweb_log_error("backend connection %p timeout; retry count exceeded", conn); fail_proxy_request(rdata); } else { nxweb_log_info("backend connection %p timeout; retrying", conn); retry_proxy_request(rdata); } }
int nxweb_listen_ssl(const char* host_and_port, int backlog, _Bool secure, const char* cert_file, const char* key_file, const char* dh_params_file, const char* cipher_priority_string) { assert(nxweb_server_config.listen_config_idx>=0 && nxweb_server_config.listen_config_idx<NXWEB_MAX_LISTEN_SOCKETS); nxweb_log_error("nxweb binding %s for http%s", host_and_port, secure?"s":""); nxweb_server_listen_config* lconf=&nxweb_server_config.listen_config[nxweb_server_config.listen_config_idx++]; lconf->listen_fd=_nxweb_bind_socket(host_and_port, backlog); if (lconf->listen_fd==-1) { return -1; } #ifdef WITH_SSL lconf->secure=secure; if (secure) { if (nxd_ssl_socket_init_server_parameters(&lconf->x509_cred, &lconf->dh_params, &lconf->priority_cache, &lconf->session_ticket_key, cert_file, key_file, dh_params_file, cipher_priority_string)==-1) return -1; } #endif // WITH_SSL return 0; }
static void on_sigterm(int sig) { nxweb_log_error("SIGTERM/SIGINT(%d) received", sig); if (shutdown_in_progress) return; shutdown_in_progress=1; // tells net_threads to finish their work //nxe_break(main_loop); // this is a little bit dirty; should modify main loop from callback int i; nxweb_net_thread_data* tdata; for (i=0, tdata=_nxweb_net_threads; i<_nxweb_num_net_threads; i++, tdata++) { /* // wake up workers pthread_mutex_lock(&tdata->job_queue_mux); pthread_cond_broadcast(&tdata->job_queue_cond); pthread_mutex_unlock(&tdata->job_queue_mux); */ nxe_trigger_eventfd(&tdata->shutdown_efs); } alarm(nxweb_server_config.shutdown_timeout); // make sure we terminate via SIGALRM if some connections do not close in 5 seconds }
static void cache_rec_unref(nxd_http_server_proto* hsp, void* req_data) { pthread_mutex_lock(&_nxweb_cache_mutex); nxweb_cache_rec* rec=req_data; assert(rec->ref_count>0); if (!--rec->ref_count) { nxe_time_t loop_time=_nxweb_net_thread_data->loop->current_time; if (loop_time > rec->expires_time || !IS_LINKED(rec)) { const char* fpath=rec->content+rec->content_length+1; ah_iter_t ci=alignhash_get(nxweb_cache, _nxweb_cache, fpath); if (ci!=alignhash_end(_nxweb_cache) && rec==alignhash_value(_nxweb_cache, ci)) { alignhash_del(nxweb_cache, _nxweb_cache, ci); cache_rec_unlink(rec); } else { nxweb_log_error("freed previously removed cache entry %s [%p]", fpath, rec); } nx_free(rec); } } cache_check_size(); pthread_mutex_unlock(&_nxweb_cache_mutex); }
static nxe_ssize_t req_body_in_write(nxe_ostream* os, nxe_istream* is, int fd, nxe_data ptr, nxe_size_t size, nxe_flags_t* flags) { //nxweb_log_error("req_body_in_write(%d)", size); nxd_http_client_proto* hcp=(nxd_http_client_proto*)((char*)os-offsetof(nxd_http_client_proto, req_body_in)); nxe_loop* loop=os->super.loop; if (hcp->state!=HCP_SENDING_BODY) { nxe_ostream_unset_ready(os); nxe_istream_set_ready(loop, &hcp->data_out); // get notified when next_os ready return 0; } hcp->req_body_sending_started=1; nxe_unset_timer(loop, NXWEB_TIMER_WRITE, &hcp->timer_write); nxe_ssize_t bytes_sent=0; if (size>0) { nxe_ostream* next_os=hcp->data_out.pair; if (next_os) { nxe_flags_t wflags=*flags; if (next_os->ready) bytes_sent=OSTREAM_CLASS(next_os)->write(next_os, &hcp->data_out, 0, (nxe_data)ptr, size, &wflags); if (!next_os->ready) { //nxweb_log_error("req_body_in_write(%d) - next_os unready", size); nxe_ostream_unset_ready(os); nxe_istream_set_ready(loop, &hcp->data_out); // get notified when next_os becomes ready again } } else { nxweb_log_error("no connected device for hcp->data_out"); nxe_ostream_unset_ready(os); } } if (*flags&NXEF_EOF && bytes_sent==size) { // end of request => get response nxe_ostream_unset_ready(os); start_receiving_response(hcp, loop); return bytes_sent; } nxe_set_timer(loop, NXWEB_TIMER_WRITE, &hcp->timer_write); return bytes_sent; }
void nxweb_run() { int i; nxweb_server_config.work_dir=getcwd(0, 0); pid_t pid=getpid(); main_thread_id=pthread_self(); _nxweb_num_net_threads=(int)sysconf(_SC_NPROCESSORS_ONLN); if (_nxweb_num_net_threads>NXWEB_MAX_NET_THREADS) _nxweb_num_net_threads=NXWEB_MAX_NET_THREADS; pthread_mutex_init(&nxweb_server_config.access_log_start_mux, 0); nxweb_access_log_restart(); struct rlimit rl_fildes; struct rlimit rl_core; getrlimit(RLIMIT_NOFILE, &rl_fildes); getrlimit(RLIMIT_CORE, &rl_core); nxweb_log_error("NXWEB startup: pid=%d net_threads=%d pg=%d" " short=%d int=%d long=%d size_t=%d evt=%d conn=%d req=%d td=%d max_fd=%d max_core=%d", (int)pid, _nxweb_num_net_threads, (int)sysconf(_SC_PAGE_SIZE), (int)sizeof(short), (int)sizeof(int), (int)sizeof(long), (int)sizeof(size_t), (int)sizeof(nxe_event), (int)sizeof(nxweb_http_server_connection), (int)sizeof(nxweb_http_request), (int)sizeof(nxweb_net_thread_data), (int)rl_fildes.rlim_cur, (int)rl_core.rlim_cur); nxweb_handler* h=nxweb_server_config.handler_list; while (h) { nxweb_log_error("handler %s [%d] registered for url: %s", h->name, h->priority, h->prefix); h=h->next; } nxweb_module* mod=nxweb_server_config.module_list; while (mod) { if (mod->on_server_startup) { if (mod->on_server_startup()) { nxweb_log_error("module %s on_server_startup() returned non-zero", mod->name); exit(EXIT_SUCCESS); // simulate normal exit so nxweb is not respawned } else { nxweb_log_error("module %s [%d] successfully initialized", mod->name, mod->priority); } } if (mod->request_dispatcher) { nxweb_server_config.request_dispatcher=mod->request_dispatcher; nxweb_log_error("module %s [%d] registered custom request dispatcher", mod->name, mod->priority); } mod=mod->next; } if (!nxweb_server_config.request_dispatcher) { nxweb_server_config.request_dispatcher=_nxweb_default_request_dispatcher; nxweb_log_error("using default request dispatcher"); } // Block signals for all threads sigset_t set; sigemptyset(&set); sigaddset(&set, SIGTERM); sigaddset(&set, SIGPIPE); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGHUP); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); if (pthread_sigmask(SIG_BLOCK, &set, NULL)) { nxweb_log_error("can't set pthread_sigmask"); exit(EXIT_SUCCESS); // simulate normal exit so nxweb is not respawned } nxweb_net_thread_data* tdata; for (i=0, tdata=_nxweb_net_threads; i<_nxweb_num_net_threads; i++, tdata++) { tdata->thread_num=i; pthread_attr_t tattr; cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(i, &cpuset); pthread_attr_init(&tattr); pthread_attr_setaffinity_np(&tattr, sizeof(cpu_set_t), &cpuset); if (pthread_create(&tdata->thread_id, &tattr, net_thread_main, tdata)) { nxweb_log_error("can't start network thread %d", i); exit(EXIT_SUCCESS); // simulate normal exit so nxweb is not respawned } pthread_attr_destroy(&tattr); } signal(SIGTERM, on_sigterm); signal(SIGINT, on_sigterm); signal(SIGUSR1, on_sigusr1); signal(SIGHUP, on_sigusr1); signal(SIGALRM, on_sigalrm); signal(SIGUSR2, on_sigusr2); // Unblock signals for the main thread; // other threads have inherited sigmask we set earlier sigdelset(&set, SIGPIPE); // except SIGPIPE if (pthread_sigmask(SIG_UNBLOCK, &set, NULL)) { nxweb_log_error("can't unset pthread_sigmask"); exit(EXIT_SUCCESS); // simulate normal exit so nxweb is not respawned } for (i=0; i<_nxweb_num_net_threads; i++) { pthread_join(_nxweb_net_threads[i].thread_id, 0); } mod=nxweb_server_config.module_list; while (mod) { if (mod->on_server_shutdown) mod->on_server_shutdown(); mod=mod->next; } nxweb_server_listen_config* lconf; for (i=0, lconf=nxweb_server_config.listen_config; i<NXWEB_MAX_LISTEN_SOCKETS; i++, lconf++) { if (lconf->listen_fd) { close(lconf->listen_fd); #ifdef WITH_SSL if (lconf->secure) nxd_ssl_socket_finalize_server_parameters(lconf->x509_cred, lconf->dh_params, lconf->priority_cache, &lconf->session_ticket_key); #endif // WITH_SSL } } for (i=0; i<NXWEB_MAX_PROXY_POOLS; i++) { if (nxweb_server_config.http_proxy_pool_config[i].saddr) _nxweb_free_addrinfo(nxweb_server_config.http_proxy_pool_config[i].saddr); } nxweb_access_log_stop(); pthread_mutex_destroy(&nxweb_server_config.access_log_start_mux); free(nxweb_server_config.work_dir); nxweb_log_error("end of nxweb_run()"); }
static void on_sigusr1(int sig) { nxweb_log_error("SIGUSR1 or SIGHUP received. Restarting access_log & error_log"); if (nxweb_server_config.error_log_fpath) nxweb_open_log_file(nxweb_server_config.error_log_fpath, 0, 0); nxweb_access_log_restart(); }
static void on_sigalrm(int sig) { nxweb_log_error("SIGALRM received. Exiting"); exit(EXIT_SUCCESS); }
static void timer_100_continue_on_timeout(nxe_timer* timer, nxe_data data) { nxd_http_client_proto* hcp=(nxd_http_client_proto*)((char*)timer-offsetof(nxd_http_client_proto, timer_100_continue)); //nxe_loop* loop=sub->super.loop; nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_100CONTINUE_TIMEOUT); nxweb_log_error("client connection %p 100-continue timeout", hcp); }
static void* net_thread_main(void* ptr) { nxweb_net_thread_data* tdata=ptr; _nxweb_net_thread_data=tdata; nxe_loop* loop=nxe_create(128); tdata->loop=loop; nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_KEEP_ALIVE, _nxe_timeouts[NXWEB_TIMER_KEEP_ALIVE]); nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_READ, _nxe_timeouts[NXWEB_TIMER_READ]); nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_WRITE, _nxe_timeouts[NXWEB_TIMER_WRITE]); nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_BACKEND, _nxe_timeouts[NXWEB_TIMER_BACKEND]); nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_100CONTINUE, _nxe_timeouts[NXWEB_TIMER_100CONTINUE]); nxe_set_timer_queue_timeout(loop, NXWEB_TIMER_ACCEPT_RETRY, _nxe_timeouts[NXWEB_TIMER_ACCEPT_RETRY]); nxweb_server_listen_config* lconf; nxweb_http_server_listening_socket* lsock; int i; for (i=0, lconf=nxweb_server_config.listen_config, lsock=tdata->listening_sock; i<NXWEB_MAX_LISTEN_SOCKETS; i++, lconf++, lsock++) { lsock->idx=i; if (lconf->listen_fd) { nxe_init_listenfd_source(&lsock->listen_source, lconf->listen_fd, NXE_PUB_DEFAULT); nxe_register_listenfd_source(loop, &lsock->listen_source); nxe_init_subscriber(&lsock->listen_sub, &listen_sub_class); nxe_subscribe(loop, &lsock->listen_source.data_notify, &lsock->listen_sub); nxe_init_timer(&lsock->accept_retry_timer, &accept_retry_timer_class); } } nxe_init_eventfd_source(&tdata->shutdown_efs, NXE_PUB_DEFAULT); nxe_register_eventfd_source(loop, &tdata->shutdown_efs); nxe_init_subscriber(&tdata->shutdown_sub, &shutdown_sub_class); nxe_subscribe(loop, &tdata->shutdown_efs.data_notify, &tdata->shutdown_sub); nxe_init_eventfd_source(&tdata->diagnostics_efs, NXE_PUB_DEFAULT); nxe_register_eventfd_source(loop, &tdata->diagnostics_efs); nxe_init_subscriber(&tdata->diagnostics_sub, &diagnostics_sub_class); nxe_subscribe(loop, &tdata->diagnostics_efs.data_notify, &tdata->diagnostics_sub); nxe_init_subscriber(&tdata->gc_sub, &gc_sub_class); nxe_subscribe(loop, &loop->gc_pub, &tdata->gc_sub); tdata->free_conn_pool=nxp_create(sizeof(nxweb_http_server_connection), 8); tdata->free_conn_nxb_pool=nxp_create(NXWEB_CONN_NXB_SIZE, 8); tdata->free_rbuf_pool=nxp_create(NXWEB_RBUF_SIZE, 2); nxw_init_factory(&tdata->workers_factory, loop); // initialize proxy pools: for (i=0; i<NXWEB_MAX_PROXY_POOLS; i++) { if (nxweb_server_config.http_proxy_pool_config[i].host) { nxd_http_proxy_pool_init(&tdata->proxy_pool[i], loop, tdata->free_conn_nxb_pool, nxweb_server_config.http_proxy_pool_config[i].host, nxweb_server_config.http_proxy_pool_config[i].saddr); } } nxweb_module* mod=nxweb_server_config.module_list; while (mod) { if (mod->on_thread_startup) { if (mod->on_thread_startup()) { nxweb_log_error("module %s on_thread_startup() returned non-zero", mod->name); exit(EXIT_SUCCESS); // simulate normal exit so nxweb is not respawned } else { nxweb_log_error("module %s [%d] network thread successfully initialized", mod->name, mod->priority); } } mod=mod->next; } nxe_run(loop); mod=nxweb_server_config.module_list; while (mod) { if (mod->on_thread_shutdown) mod->on_thread_shutdown(); mod=mod->next; } nxp_destroy(tdata->free_conn_pool); nxp_destroy(tdata->free_conn_nxb_pool); nxp_destroy(tdata->free_rbuf_pool); /* for (i=0; i<NXWEB_NUM_PROXY_POOLS; i++) { if (nxweb_server_config.http_proxy_pool_config[i].host) nxd_http_proxy_pool_finalize(&tdata->proxy_pool[i]); } */ nxe_destroy(loop); nxweb_log_error("network thread clean exit"); nxweb_access_log_thread_flush(); return 0; }
static void data_in_do_read(nxe_ostream* os, nxe_istream* is) { nxd_http_client_proto* hcp=(nxd_http_client_proto*)((char*)os-offsetof(nxd_http_client_proto, data_in)); nxe_loop* loop=os->super.loop; if (hcp->state==HCP_WAITING_FOR_RESPONSE) { nxe_unset_timer(loop, NXWEB_TIMER_READ, &hcp->timer_read); nxe_unset_timer(loop, NXWEB_TIMER_100CONTINUE, &hcp->timer_100_continue); nxe_set_timer(loop, NXWEB_TIMER_READ, &hcp->timer_read); nxb_make_room(hcp->nxb, NXWEB_MAX_REQUEST_HEADERS_SIZE); hcp->state=HCP_RECEIVING_HEADERS; } if (hcp->state==HCP_RECEIVING_HEADERS) { int size; nxe_flags_t flags=0; void* ptr=nxb_get_room(hcp->nxb, &size); int bytes_received=ISTREAM_CLASS(is)->read(is, os, ptr, size, &flags); if (bytes_received) { nxb_blank_fast(hcp->nxb, bytes_received); int read_buf_size; char* read_buf=nxb_get_unfinished(hcp->nxb, &read_buf_size); char* end_of_headers; char* start_of_body; if ((end_of_headers=_nxweb_find_end_of_http_headers(read_buf, read_buf_size, &start_of_body))) { nxb_finish_stream(hcp->nxb, 0); memset(&hcp->resp, 0, sizeof(hcp->resp)); hcp->resp.nxb=hcp->nxb; hcp->resp.cdstate.monitor_only=hcp->chunked_do_not_decode; if (_nxweb_parse_http_response(&hcp->resp, read_buf, end_of_headers)) { // bad response nxe_unset_timer(loop, NXWEB_TIMER_READ, &hcp->timer_read); nxe_ostream_unset_ready(os); nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_BAD_RESPONSE); return; } if (hcp->receiving_100_continue) { hcp->receiving_100_continue=0; if (hcp->resp.status_code==100) { // back to sending request body nxe_ostream_unset_ready(&hcp->data_in); nxe_unset_timer(loop, NXWEB_TIMER_READ, &hcp->timer_read); nxe_set_timer(loop, NXWEB_TIMER_WRITE, &hcp->timer_write); nxe_istream_set_ready(loop, &hcp->data_out); hcp->state=HCP_SENDING_BODY; return; } else { nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_NO_100CONTINUE); } } char* read_buf_end=read_buf+read_buf_size; if (start_of_body<read_buf_end) { hcp->first_body_chunk=start_of_body; hcp->first_body_chunk_end=read_buf_end; nxe_size_t first_body_chunk_size=hcp->first_body_chunk_end-hcp->first_body_chunk; if (hcp->resp.chunked_encoding) { int r=_nxweb_decode_chunked_stream(&hcp->resp.cdstate, hcp->first_body_chunk, &first_body_chunk_size); hcp->first_body_chunk_end=hcp->first_body_chunk+first_body_chunk_size; if (r<0) nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_RESPONSE_CHUNKED_ENCODING_ERROR); else if (r>0) hcp->response_body_complete=1; } else if (first_body_chunk_size >= hcp->resp.content_length && hcp->resp.content_length>=0) { hcp->response_body_complete=1; } } else { hcp->first_body_chunk=0; hcp->first_body_chunk_end=0; } nxe_ostream_unset_ready(os); nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_RESPONSE_RECEIVED); if (hcp->resp.content_length && !hcp->req->head_method) { // is body expected? hcp->state=HCP_RECEIVING_BODY; nxe_istream_set_ready(loop, &hcp->resp_body_out); if (hcp->resp_body_out.pair) { nxe_ostream_set_ready(loop, os); } } else { // rearm connection request_complete(hcp, loop); } } else { if (read_buf_size>=NXWEB_MAX_REQUEST_HEADERS_SIZE) { // bad response (headers too large) nxe_unset_timer(loop, NXWEB_TIMER_READ, &hcp->timer_read); nxe_ostream_unset_ready(os); nxe_publish(&hcp->events_pub, (nxe_data)NXD_HCP_BAD_RESPONSE); return; } } } } else if (hcp->state==HCP_RECEIVING_BODY) { nxe_ostream* next_os=hcp->resp_body_out.pair; if (next_os) { if (next_os->ready) OSTREAM_CLASS(next_os)->do_read(next_os, &hcp->resp_body_out); if (!next_os->ready) { nxe_ostream_unset_ready(os); nxe_istream_set_ready(loop, &hcp->resp_body_out); // get notified when next_os becomes ready again } } else { nxweb_log_error("no connected device for hcp->resp_body_out"); nxe_ostream_unset_ready(os); } } }
nxweb_result nxweb_cache_store_response(nxweb_http_server_connection* conn, nxweb_http_response* resp) { nxe_time_t loop_time=nxweb_get_loop_time(conn); if (!resp->status_code) resp->status_code=200; if (resp->status_code==200 && resp->sendfile_path // only cache content from files && resp->content_length>=0 && resp->content_length<=NXWEB_MAX_CACHED_ITEM_SIZE // must be small && resp->sendfile_offset==0 && resp->sendfile_end==resp->content_length // whole file only && resp->sendfile_end>=resp->sendfile_info.st_size // st_size could be zero if not initialized && alignhash_size(_nxweb_cache)<NXWEB_MAX_CACHED_ITEMS+16) { const char* fpath=resp->sendfile_path; const char* key=resp->cache_key; if (nxweb_cache_try(conn, resp, key, 0, resp->last_modified)!=NXWEB_MISS) return NXWEB_OK; nxweb_cache_rec* rec=nx_calloc(sizeof(nxweb_cache_rec)+resp->content_length+1+strlen(key)+1); rec->expires_time=loop_time+NXWEB_DEFAULT_CACHED_TIME; rec->last_modified=resp->last_modified; rec->content_type=resp->content_type; // assume content_type and content_charset come rec->content_charset=resp->content_charset; // from statically allocated memory, which won't go away rec->content_length=resp->content_length; rec->gzip_encoded=resp->gzip_encoded; char* ptr=((char*)rec)+offsetof(nxweb_cache_rec, content); int fd; if ((fd=open(fpath, O_RDONLY))<0 || read(fd, ptr, resp->content_length)!=resp->content_length) { if (fd>0) close(fd); nx_free(rec); nxweb_log_error("nxweb_cache_file_store_and_send(): [%s] stat() was OK, but open/read() failed", fpath); nxweb_send_http_error(resp, 500, "Internal Server Error"); return NXWEB_ERROR; } close(fd); resp->content=ptr; ptr+=resp->content_length; *ptr++='\0'; strcpy(ptr, key); key=ptr; int ret=0; ah_iter_t ci; pthread_mutex_lock(&_nxweb_cache_mutex); ci=alignhash_set(nxweb_cache, _nxweb_cache, key, &ret); if (ci!=alignhash_end(_nxweb_cache)) { if (ret!=AH_INS_ERR) { alignhash_value(_nxweb_cache, ci)=rec; cache_rec_link(rec); rec->ref_count++; cache_check_size(); pthread_mutex_unlock(&_nxweb_cache_mutex); nxweb_log_info("memcached %s", key); conn->hsp.req_data=rec; assert(!conn->hsp.req_finalize); conn->hsp.req_finalize=cache_rec_unref; //nxweb_start_sending_response(conn, resp); return NXWEB_OK; } else { // AH_INS_ERR => key already exists (added by other thread) nx_free(rec); rec=alignhash_value(_nxweb_cache, ci); resp->content_length=rec->content_length; resp->content=rec->content; resp->content_type=rec->content_type; resp->content_charset=rec->content_charset; resp->last_modified=rec->last_modified; resp->gzip_encoded=rec->gzip_encoded; rec->ref_count++; pthread_mutex_unlock(&_nxweb_cache_mutex); conn->hsp.req_data=rec; assert(!conn->hsp.req_finalize); conn->hsp.req_finalize=cache_rec_unref; //nxweb_start_sending_response(conn, resp); return NXWEB_OK; } } pthread_mutex_unlock(&_nxweb_cache_mutex); nx_free(rec); } return NXWEB_OK; }
int nxweb_select_handler(nxweb_http_server_connection* conn, nxweb_http_request* req, nxweb_http_response* resp, nxweb_handler* handler, nxe_data handler_param) { conn->handler=handler; conn->handler_param=handler_param; // since nxweb_select_handler() could be called several times // make sure all changed fields returned to initial state time_t if_modified_since_original=req->if_modified_since; // save original value const char* uri_original=req->uri; const int num_filters=handler->num_filters; nxweb_filter** filters=handler->filters; if (num_filters) { // init filters int i; nxweb_filter* filter; nxweb_filter_data* fdata; for (i=0; i<num_filters; i++) { filter=filters[i]; assert(filter->init); req->filter_data[i]=filter->init(filter, conn, req, resp); } // let filters decode uri const char* uri=req->uri; for (i=num_filters-1; i>=0; i--) { fdata=req->filter_data[i]; if (!fdata || fdata->bypass) continue; filter=filters[i]; if (!filter->decode_uri) continue; uri=filter->decode_uri(filter, conn, req, resp, req->filter_data[i], uri); // should return the same uri if unchanged } if (req->uri!=uri) { // uri changed if (handler->prefix_len && !nxweb_url_prefix_match(uri, strlen(uri), handler->prefix, handler->prefix_len)) { // ensure it still matches nxweb_log_error("uri %s doesn't match prefix %s after decode", uri, handler->prefix); } req->uri=uri; } } req->path_info=req->uri+handler->prefix_len; // check cache if (req->get_method && !req->content_length && handler->on_generate_cache_key) { // POST requests are not cacheable /* * Cache_key is unique ID for each request with all its parameters, headers and options * that could affect the response. * Cache_key must conform to file path syntax, as many filters are going to use it as a file name. * Handlers will typically use full request uri (host, port, ssl, path, query string) * as initial key (file-path-encoded, see note above). * Each filter shall add its own differentiators to the key in translate_cache_key() method * regardless of whether it implements itself caching or not. * E.g. gzip filter must differentiate requests coming with accept_gzip_encoding flag set * from requests without that flag. So it is going to append '$gzip' suffix to all requests * that it could possibly compress (it might not compress some content at the end * for various reasons, e.g. flags in response, but those is not going to affect the cache_key). * Each filter can have its own cache_key. Cache_key of a filter is not affected by filters * coming after it in filter chain. */ if (handler->on_generate_cache_key(conn, req, resp)==NXWEB_NEXT) return NXWEB_NEXT; if (resp->cache_key && *resp->cache_key) { const char* cache_key=resp->cache_key; if (num_filters) { int i; nxweb_filter* filter; nxweb_filter_data* fdata; for (i=0; i<num_filters; i++) { fdata=req->filter_data[i]; if (!fdata || fdata->bypass) continue; filter=filters[i]; if (!filter->translate_cache_key) continue; if (filter->translate_cache_key(filter, conn, req, resp, fdata, cache_key)==NXWEB_NEXT) continue; cache_key=fdata->cache_key; assert(cache_key && *cache_key); } resp->cache_key=cache_key; } if (handler->memcache) { nxweb_result res=nxweb_cache_try(conn, resp, resp->cache_key, req->if_modified_since, 0); if (res==NXWEB_OK) { conn->hsp.cls->start_sending_response(&conn->hsp, resp); return NXWEB_OK; } else if (res==NXWEB_REVALIDATE) { // fall through } else if (res!=NXWEB_MISS) return res; } if (resp->last_modified) { // in case one of filters has already determined resp->last_modified if (req->if_modified_since && resp->last_modified<=req->if_modified_since) { resp->status_code=304; resp->status="Not Modified"; conn->hsp.cls->start_sending_response(&conn->hsp, resp); return NXWEB_OK; } } if (num_filters) { time_t check_time=resp->last_modified? resp->last_modified : nxe_get_current_http_time(conn->tdata->loop); int i; nxweb_filter* filter; nxweb_filter_data* fdata; for (i=num_filters-1; i>=0; i--) { filter=filters[i]; fdata=req->filter_data[i]; if (filter->serve_from_cache && fdata && !fdata->bypass) { nxweb_result r=filter->serve_from_cache(filter, conn, req, resp, fdata, check_time); if (r==NXWEB_OK) { // filter has served content (which has not expired by check_time) // process it through filters & send to client for (i++; i<num_filters; i++) { filter=filters[i]; nxweb_filter_data* fdata1=req->filter_data[i]; if (fdata1 && !fdata1->bypass && filter->do_filter) { if (filter->do_filter(filter, conn, req, resp, fdata1)==NXWEB_DELAY) { resp->run_filter_idx=i+1; // resume from next filter return NXWEB_OK; } } } if (handler->memcache) { nxweb_cache_store_response(conn, resp); } conn->hsp.cls->start_sending_response(&conn->hsp, resp); return NXWEB_OK; } /* else if (r==NXWEB_REVALIDATE) { // filter has content but it has expired // the filter has already set if_modified_since field in request (revalidation mode) // it must be ready to process 304 Not Modified response // on the way back in its do_filter() } else { // no cached content OR cached content's last_modified is older than req->if_modified_since } */ } } } } } nxweb_result r=NXWEB_OK; if (handler->on_select) r=handler->on_select(conn, req, resp); if (r!=NXWEB_OK) { if (num_filters) { // filters have been initialized => finalize them int i; nxweb_filter* filter; nxweb_filter_data* fdata; for (i=0; i<num_filters; i++) { filter=filters[i]; fdata=req->filter_data[i]; if (fdata && filter->finalize) filter->finalize(filter, conn, req, resp, fdata); req->filter_data[i]=0; // call no more } } // restore saved fields req->uri=uri_original; req->if_modified_since=if_modified_since_original; // reset changed fields conn->handler=0; conn->handler_param=(nxe_data)0; resp->cache_key=0; resp->last_modified=0; resp->mtype=0; resp->content_type=0; resp->content_charset=0; resp->sendfile_path=0; if (resp->sendfile_fd>0) { close(resp->sendfile_fd); } resp->sendfile_fd=0; if (resp->sendfile_info.st_ino) memset(&resp->sendfile_info, 0, sizeof(resp->sendfile_info)); } return r; }