Esempio n. 1
0
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;
    }
  }
}
Esempio n. 2
0
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");
}
Esempio n. 3
0
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");
}
Esempio n. 4
0
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();
}
Esempio n. 5
0
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;
}
Esempio n. 6
0
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;
}
Esempio n. 7
0
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;
}
Esempio n. 8
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);
}
Esempio n. 9
0
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);
  }
}
Esempio n. 10
0
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");
  }
}
Esempio n. 11
0
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));
    }
  }
}
Esempio n. 12
0
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;
}
Esempio n. 13
0
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;
}
Esempio n. 14
0
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);
}
Esempio n. 15
0
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;
    }
  }
}
Esempio n. 16
0
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;
}
Esempio n. 17
0
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);
}
Esempio n. 18
0
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);
  }
}
Esempio n. 19
0
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;
}
Esempio n. 20
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
}
Esempio n. 21
0
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);
}
Esempio n. 22
0
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;
}
Esempio n. 23
0
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()");
}
Esempio n. 24
0
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();
}
Esempio n. 25
0
static void on_sigalrm(int sig) {
  nxweb_log_error("SIGALRM received. Exiting");
  exit(EXIT_SUCCESS);
}
Esempio n. 26
0
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);
}
Esempio n. 27
0
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;
}
Esempio n. 28
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);
    }
  }
}
Esempio n. 29
0
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;
}
Esempio n. 30
0
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;
}