Plack-Handler-H2

 view release on metacpan or  search on metacpan

lib/Plack/Handler/plack_handler_h2.cc  view on Meta::CPAN

    if (!ecdh) {
      errx(1, "EC_KEY_new_by_curv_name failed: %s",
           ERR_error_string(ERR_get_error(), NULL));
    }
    SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
    EC_KEY_free(ecdh);
  }
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */

  if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file.c_str(),
                                  SSL_FILETYPE_PEM) != 1) {
    croak("Could not read private key file");
  }
  if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file.c_str()) != 1) {
    croak("Could not read certificate file");
  }

  SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);

  return ssl_ctx;
}

static inline bool ends_with(const std::string &str,
                             const std::string &suffix) {
  if (str.length() < suffix.length()) {
    return false;
  }
  return str.compare(str.length() - suffix.length(), suffix.length(), suffix) ==
         0;
}

static inline std::string double_dot = "/..";
static inline std::string single_dot = "/.";
static inline int check_path(std::string &path) {
  /* We don't like '\' in url. */
  return path[0] && path[0] == '/' && strchr(path.c_str(), '\\') == NULL &&
         strstr(path.c_str(), double_dot.c_str()) == NULL &&
         strstr(path.c_str(), single_dot.c_str()) == NULL &&
         !ends_with(path, double_dot) && !ends_with(path, single_dot);
}

static inline H2Session *get_session_from_sv_uv(SV *session_sv) {
  if (!SvOK(session_sv) || !SvIOK(session_sv)) {
    warn("Session SV is not OK (undefined or invalid)");
    return nullptr;
  }

  uintptr_t session_ptr = SvUV(session_sv);

  if (session_ptr == 0) {
    warn("Session pointer is null");
    return nullptr;
  }

  return reinterpret_cast<H2Session *>(session_ptr);
}

static inline int32_t send_headers_from_av(H2Session *session,
                                           int32_t stream_id, SV *status_sv,
                                           SV *headers_sv,
                                           bool streaming = false) {
  int status = SvIV(status_sv);
  AV *headers_av =
      (headers_sv && SvROK(headers_sv)) ? (AV *)SvRV(headers_sv) : nullptr;

  auto data_it = std::find_if(
      session->data.begin(), session->data.end(),
      [stream_id](H2Data *d) { return d->stream_id == stream_id; });
  if (data_it == session->data.end()) {
    croak("Stream data not found for stream_id %d", stream_id);
  }

  H2Data *data = *data_it;
  auto &header_storage = *data->response_headers;

  header_storage.clear();

  std::vector<nghttp2_nv> nva;

  header_storage.push_back(":status");
  header_storage.push_back(std::to_string(status));

  nghttp2_nv status_header = {
      (uint8_t *)header_storage[header_storage.size() - 2].c_str(),
      (uint8_t *)header_storage[header_storage.size() - 1].c_str(), 7,
      header_storage[header_storage.size() - 1].length(), NGHTTP2_NV_FLAG_NONE};
  nva.push_back(status_header);

  // Add other headers
  if (headers_av) {
    int header_count = av_len(headers_av) + 1;
    for (int i = 0; i < header_count; i += 2) {
      SV **name_sv = av_fetch(headers_av, i, 0);
      SV **value_sv = av_fetch(headers_av, i + 1, 0);

      if (name_sv && value_sv) {
        size_t name_len, value_len;
        const char *name = SvPV(*name_sv, name_len);
        const char *value = SvPV(*value_sv, value_len);

        std::string lowercase_name(name, name_len);
        std::transform(lowercase_name.begin(), lowercase_name.end(),
                       lowercase_name.begin(), ::tolower);

        // Skip content-length for streaming responses (when with_end_stream is
        // false) HTTP/2 doesn't need content-length for streaming
        if (streaming && lowercase_name == "content-length") {
          continue;
        }

        header_storage.push_back(lowercase_name);
        header_storage.push_back(std::string(value, value_len));

        nghttp2_nv header = {
            (uint8_t *)header_storage[header_storage.size() - 2].c_str(),
            (uint8_t *)header_storage[header_storage.size() - 1].c_str(),
            lowercase_name.length(), value_len, NGHTTP2_NV_FLAG_NONE};
        nva.push_back(header);
      }
    }
  }

  nghttp2_submit_headers(session->session.get(), NGHTTP2_FLAG_END_HEADERS,
                         stream_id, NULL, nva.data(), nva.size(), NULL);

  return session->send();
}

static const char RESPONSE_500[] =
    "<html><head><title>500</title></head><body><h1>500 Internal Server "
    "Error</h1></body></html>";
static inline int32_t error_reply(H2Session *session, H2Data *data) {
  int pipefd[2];
  int rv = pipe(pipefd);
  nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")};
  if (rv != 0) {
    warn("pipe() failed, errno=%d", errno);
    rv = nghttp2_submit_rst_stream(session->session.get(), NGHTTP2_FLAG_NONE,
                                   data->stream_id, NGHTTP2_INTERNAL_ERROR);
    if (rv != 0) {
      warn("nghttp2_submit_rst_stream() failed, rv=%d", rv);
      return -1;
    }
    return NGHTTP2_ERR_CALLBACK_FAILURE;
  }

  ssize_t writelen = write(pipefd[1], RESPONSE_500, sizeof(RESPONSE_500) - 1);
  close(pipefd[1]);

  if (writelen != sizeof(RESPONSE_500) - 1) {
    close(pipefd[0]);
    warn("write() failed, errno=%d", errno);
    return -1;
  }

  data->fd = pipefd[0];

  rv = nghttp2_submit_headers(session->session.get(), 0, data->stream_id, NULL,
                              hdrs, sizeof(hdrs) / sizeof(hdrs[0]), NULL);

  if (rv != 0) {
    warn("nghttp2_submit_headers() failed: %s", nghttp2_strerror(rv));
    close(data->fd);
    return -1;
  }

  // Create the data provider

lib/Plack/Handler/plack_handler_h2.cc  view on Meta::CPAN

            newSVpv(session->client_address.c_str(),
                    session->client_address.length()));
  hv_stores(env, "QUERY_STRING",
            newSVpv(data->query ? data->query->c_str() : "",
                    data->query ? data->query->length() : 0));
  hv_stores(env, "HTTPS", newSVpvs("on"));

  const auto &headers = *data->headers;
  std::string header_prefix = "HTTP_";
  for (const auto &[key, value] : headers) {
    std::string header_name = "";
    for (char c : key) {
      if (c == '-') {
        header_name += '_';
      } else {
        header_name += std::toupper(c);
      }
    }

    // Plack's convention is to store CONTENT_LENGTH and CONTENT_TYPE without
    // the HTTP_ prefix
    if (header_name != "CONTENT_LENGTH" && header_name != "CONTENT_TYPE") {
      header_name = header_prefix + header_name;
    }

    SV *header_value = newSVpv(value.c_str(), value.length());
    hv_store(env, header_name.c_str(), header_name.length(), header_value, 0);
  }

  // Append HTTP/2 pseudo-headers
  {
    SV *header_value;
    std::string h2_header_name = "HTTP_2_";
    std::string authority = data->authority ? *data->authority : "";
    header_value = newSVpv(authority.c_str(), authority.length());
    hv_store(env, (h2_header_name + "AUTHORITY").c_str(),
             (h2_header_name + "AUTHORITY").length(), header_value, 0);
    std::string scheme = data->scheme ? *data->scheme : "https";
    header_value = newSVpv(scheme.c_str(), scheme.length());
    hv_store(env, (h2_header_name + "SCHEME").c_str(),
             (h2_header_name + "SCHEME").length(), header_value, 0);
    std::string path = data->path ? *data->path : "/";
    header_value = newSVpv(path.c_str(), path.length());
    hv_store(env, (h2_header_name + "PATH").c_str(),
             (h2_header_name + "PATH").length(), header_value, 0);
    std::string method = data->method ? *data->method : "GET";
    header_value = newSVpv(method.c_str(), method.length());
    hv_store(env, (h2_header_name + "METHOD").c_str(),
             (h2_header_name + "METHOD").length(), header_value, 0);
  }

  AV *version = newAV();
  av_store(version, 0, newSViv(1));
  av_store(version, 1, newSViv(1));
  hv_stores(env, "psgi.version", newRV_noinc((SV *)version));
  hv_stores(env, "psgi.url_scheme", newSVpvs("https"));
  hv_stores(env, "psgi.errors", newRV_inc((SV *)PL_stderrgv));
  hv_stores(env, "psgi.multithread", &PL_sv_no);
  hv_stores(env, "psgi.multiprocess", &PL_sv_yes);
  hv_stores(env, "psgi.run_once", &PL_sv_no);
  hv_stores(env, "psgi.streaming", &PL_sv_yes);
  hv_stores(env, "psgi.nonblocking", &PL_sv_no);
  hv_stores(env, "psgix.h2.stream_id", newSViv(data->stream_id));

  SV *input_sv = nullptr;
  if (data->body_fd != -1) {
    lseek(data->body_fd, 0, SEEK_SET); // Rewind to start
    PerlIO *pio = PerlIO_fdopen(data->body_fd, "r");
    if (pio) {
      GV *gv = newGVgen("Plack::Handler::H2");
      IO *io = GvIOn(gv);
      IoIFP(io) = pio;
      IoOFP(io) = pio;
      IoTYPE(io) = IoTYPE_RDONLY;
      input_sv = newRV_noinc((SV *)gv);
      data->body_fd = -1;
    } else {
      warn("Failed to create PerlIO from fd: %s", strerror(errno));
      input_sv = newSVpvs("");
    }
  } else if (data->body_buffer && !data->body_buffer->empty()) {
    int pipefd[2];
    if (pipe(pipefd) == 0) {
      ssize_t written = write(pipefd[1], data->body_buffer->c_str(),
                              data->body_buffer->size());
      close(pipefd[1]);
      if (written == (ssize_t)data->body_buffer->size()) {
        PerlIO *pio = PerlIO_fdopen(pipefd[0], "r");
        if (pio) {
          GV *gv = newGVgen("Plack::Handler::H2");
          IO *io = GvIOn(gv);
          IoIFP(io) = pio;
          IoOFP(io) = pio;
          IoTYPE(io) = IoTYPE_RDONLY;
          input_sv = newRV_noinc((SV *)gv);
        } else {
          close(pipefd[0]);
          input_sv = newSVpvs("");
        }
      } else {
        close(pipefd[0]);
        input_sv = newSVpvs("");
      }
    } else {
      input_sv = newSVpvs("");
    }
  } else {
    int pipefd[2];
    if (pipe(pipefd) == 0) {
      close(pipefd[1]);
      PerlIO *pio = PerlIO_fdopen(pipefd[0], "r");
      if (pio) {
        GV *gv = newGVgen("Plack::Handler::H2");
        IO *io = GvIOn(gv);
        IoIFP(io) = pio;
        IoOFP(io) = pio;
        IoTYPE(io) = IoTYPE_RDONLY;
        input_sv = newRV_noinc((SV *)gv);
      } else {
        close(pipefd[0]);
        input_sv = newSVpvs("");



( run in 0.755 second using v1.01-cache-2.11-cpan-0b5f733616e )