diff options
-rw-r--r-- | src/plugins/hs_apps/http_cli.c | 34 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_client.c | 33 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_client_cli.c | 16 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_tps.c | 35 | ||||
-rw-r--r-- | src/plugins/hs_apps/proxy.c | 42 | ||||
-rw-r--r-- | src/plugins/hs_apps/proxy.h | 3 | ||||
-rw-r--r-- | src/plugins/hs_apps/vcl/vcl_test_protos.c | 13 | ||||
-rw-r--r-- | src/plugins/http/http.c | 152 | ||||
-rw-r--r-- | src/plugins/http/http.h | 110 | ||||
-rw-r--r-- | src/plugins/http/http_plugin.rst | 54 | ||||
-rw-r--r-- | src/plugins/http_static/http_static.h | 6 | ||||
-rw-r--r-- | src/plugins/http_static/static_server.c | 63 |
12 files changed, 289 insertions, 272 deletions
diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index d670ae6b369..531e2750c1e 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -52,7 +52,8 @@ typedef struct u32 tx_offset; u32 vpp_session_index; http_header_table_t req_headers; - http_header_t *resp_headers; + http_headers_ctx_t resp_headers; + u8 *resp_headers_buf; } hcs_session_t; typedef struct @@ -92,6 +93,7 @@ hcs_session_alloc (u32 thread_index) memset (hs, 0, sizeof (*hs)); hs->session_index = hs - hcm->sessions[thread_index]; hs->thread_index = thread_index; + vec_validate (hs->resp_headers_buf, 255); return hs; } @@ -172,21 +174,10 @@ start_send_data (hcs_session_t *hs, http_status_code_t status) { http_msg_t msg; session_t *ts; - u8 *headers_buf = 0; int rv; - if (vec_len (hs->resp_headers)) - { - headers_buf = http_serialize_headers (hs->resp_headers); - vec_reset_length (hs->resp_headers); - msg.data.headers_offset = 0; - msg.data.headers_len = vec_len (headers_buf); - } - else - { - msg.data.headers_offset = 0; - msg.data.headers_len = 0; - } + msg.data.headers_offset = 0; + msg.data.headers_len = hs->resp_headers.tail_offset; msg.type = HTTP_MSG_REPLY; msg.code = status; @@ -201,9 +192,9 @@ start_send_data (hcs_session_t *hs, http_status_code_t status) if (msg.data.headers_len) { - rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf); + rv = svm_fifo_enqueue (ts->tx_fifo, msg.data.headers_len, + hs->resp_headers.buf); ASSERT (rv == msg.data.headers_len); - vec_free (headers_buf); } if (!msg.data.body_len) @@ -245,8 +236,7 @@ send_data_to_http (void *rpc_args) if (args->plain_text) type = HTTP_CONTENT_TEXT_PLAIN; - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_add_header (&hs->resp_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (type)); start_send_data (hs, HTTP_STATUS_OK); @@ -371,7 +361,8 @@ hcs_ts_rx_callback (session_t *ts) hs = hcs_session_get (ts->thread_index, ts->opaque); hs->tx_buf = 0; - vec_reset_length (hs->resp_headers); + http_init_headers_ctx (&hs->resp_headers, hs->resp_headers_buf, + vec_len (hs->resp_headers_buf)); http_reset_header_table (&hs->req_headers); /* Read the http message header */ @@ -380,8 +371,7 @@ hcs_ts_rx_callback (session_t *ts) if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET) { - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_ALLOW), + http_add_header (&hs->resp_headers, HTTP_HEADER_ALLOW, http_token_lit ("GET")); start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED); goto done; @@ -540,7 +530,7 @@ hcs_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf) return; vec_free (hs->tx_buf); - vec_free (hs->resp_headers); + vec_free (hs->resp_headers_buf); http_free_header_table (&hs->req_headers); hcs_session_free (hs); } diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 91ac6cf0032..20271fc4aea 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -6,7 +6,6 @@ #include <vnet/session/application_interface.h> #include <vnet/session/session.h> #include <http/http.h> -#include <http/http_header_names.h> #include <http/http_content_types.h> #include <http/http_status_codes.h> #include <vppinfra/unix.h> @@ -34,7 +33,7 @@ typedef struct u32 thread_index; vlib_main_t *vlib_main; u8 *headers_buf; - http_header_t *req_headers; + http_headers_ctx_t req_headers; http_msg_t msg; } hc_worker_t; @@ -155,9 +154,9 @@ hc_request (session_t *s, session_error_t err) rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hcm->target), hcm->target); ASSERT (rv == vec_len (hcm->target)); - rv = svm_fifo_enqueue (s->tx_fifo, vec_len (wrk->headers_buf), + rv = svm_fifo_enqueue (s->tx_fifo, wrk->req_headers.tail_offset, wrk->headers_buf); - ASSERT (rv == wrk->msg.data.headers_len); + ASSERT (rv == wrk->req_headers.tail_offset); if (hcm->req_method == HTTP_REQ_POST) { @@ -214,22 +213,22 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, { if (hcm->is_file) http_add_header ( - &wrk->req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + &wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); else http_add_header ( - &wrk->req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + &wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (HTTP_CONTENT_APP_X_WWW_FORM_URLENCODED)); } + http_add_header (&wrk->req_headers, HTTP_HEADER_ACCEPT, "*", 1); vec_foreach (header, hcm->custom_header) - http_add_header (&wrk->req_headers, (const char *) header->name, - vec_len (header->name), (const char *) header->value, - vec_len (header->value)); - - wrk->headers_buf = http_serialize_headers (wrk->req_headers); - vec_free (wrk->req_headers); + http_add_custom_header ( + &wrk->req_headers, (const char *) header->name, vec_len (header->name), + (const char *) header->value, vec_len (header->value)); + clib_warning ("%U", format_http_bytes, wrk->headers_buf, + wrk->req_headers.tail_offset); wrk->msg.method_type = hcm->req_method; if (hcm->req_method == HTTP_REQ_POST) wrk->msg.data.body_len = vec_len (hcm->data); @@ -240,7 +239,7 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, /* request target */ wrk->msg.data.target_path_len = vec_len (hcm->target); /* custom headers */ - wrk->msg.data.headers_len = vec_len (wrk->headers_buf); + wrk->msg.data.headers_len = wrk->req_headers.tail_offset; /* total length */ wrk->msg.data.len = wrk->msg.data.target_path_len + wrk->msg.data.headers_len + wrk->msg.data.body_len; @@ -621,7 +620,13 @@ hc_run (vlib_main_t *vm) num_threads = 1 /* main thread */ + vtm->n_threads; vec_validate (hcm->wrk, num_threads - 1); vec_foreach (wrk, hcm->wrk) - wrk->thread_index = wrk - hcm->wrk; + { + wrk->thread_index = wrk - hcm->wrk; + /* 4k for headers should be enough */ + vec_validate (wrk->headers_buf, 4095); + http_init_headers_ctx (&wrk->req_headers, wrk->headers_buf, + vec_len (wrk->headers_buf)); + } if ((err = hc_attach ())) return clib_error_return (0, "http client attach: %U", format_clib_error, diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index 3c50e24c9fd..4ee3b49444c 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -16,7 +16,6 @@ #include <vnet/session/application_interface.h> #include <vnet/session/session.h> #include <http/http.h> -#include <http/http_header_names.h> #include <http/http_content_types.h> #include <http/http_status_codes.h> @@ -37,7 +36,6 @@ typedef struct u32 vpp_session_index; u64 to_recv; u8 is_closed; - http_header_t *req_headers; } hcc_session_t; typedef struct @@ -131,9 +129,10 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, hcc_session_t *hs, *new_hs; hcc_worker_t *wrk; http_msg_t msg; - u8 *headers_buf; + u8 *headers_buf = 0; u32 new_hs_index; int rv; + http_headers_ctx_t headers; HCC_DBG ("ho hc_index: %d", hc_index); @@ -157,11 +156,10 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, HCC_DBG ("new hc_index: %d", new_hs->session_index); as->opaque = new_hs_index; - http_add_header (&new_hs->req_headers, - http_header_name_token (HTTP_HEADER_ACCEPT), + vec_validate (headers_buf, 63); + http_init_headers_ctx (&headers, headers_buf, vec_len (headers_buf)); + http_add_header (&headers, HTTP_HEADER_ACCEPT, http_content_type_token (HTTP_CONTENT_TEXT_HTML)); - headers_buf = http_serialize_headers (new_hs->req_headers); - vec_free (new_hs->req_headers); msg.type = HTTP_MSG_REQUEST; msg.method_type = HTTP_REQ_GET; @@ -170,7 +168,7 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, msg.data.target_path_len = vec_len (hcm->http_query); /* custom headers */ msg.data.headers_offset = msg.data.target_path_len; - msg.data.headers_len = vec_len (headers_buf); + msg.data.headers_len = headers.tail_offset; /* request body */ msg.data.body_len = 0; /* data type and total length */ @@ -180,7 +178,7 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, { hcm->http_query, vec_len (hcm->http_query) }, - { headers_buf, vec_len (headers_buf) } }; + { headers_buf, msg.data.headers_len } }; rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */); vec_free (headers_buf); diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c index f4ef808e410..59a0309e363 100644 --- a/src/plugins/hs_apps/http_tps.c +++ b/src/plugins/hs_apps/http_tps.c @@ -17,7 +17,6 @@ #include <vnet/session/application_interface.h> #include <vnet/session/session.h> #include <http/http.h> -#include <http/http_header_names.h> #include <http/http_content_types.h> #define HTS_RX_BUF_SIZE (64 << 10) @@ -41,7 +40,8 @@ typedef struct }; u8 *uri; u8 *rx_buf; - http_header_t *resp_headers; + http_headers_ctx_t resp_headers; + u8 *resp_headers_buf; } hts_session_t; typedef struct hts_listen_cfg_ @@ -86,6 +86,7 @@ hts_session_alloc (u32 thread_index) pool_get_zero (htm->sessions[thread_index], hs); hs->session_index = hs - htm->sessions[thread_index]; hs->thread_index = thread_index; + vec_validate (hs->resp_headers_buf, 255); return hs; } @@ -111,6 +112,7 @@ hts_session_free (hts_session_t *hs) clib_warning ("Freeing session %u", hs->session_index); vec_free (hs->rx_buf); + vec_free (hs->resp_headers_buf); if (CLIB_DEBUG) clib_memset (hs, 0xfa, sizeof (*hs)); @@ -233,26 +235,20 @@ hts_start_send_data (hts_session_t *hs, http_status_code_t status) { http_msg_t msg; session_t *ts; - u8 *headers_buf = 0; u32 n_segs = 1; svm_fifo_seg_t seg[2]; int rv; - if (vec_len (hs->resp_headers)) + msg.data.headers_offset = 0; + msg.data.headers_len = 0; + + if (hs->resp_headers.tail_offset) { - headers_buf = http_serialize_headers (hs->resp_headers); - vec_free (hs->resp_headers); - msg.data.headers_offset = 0; - msg.data.headers_len = vec_len (headers_buf); - seg[1].data = headers_buf; + msg.data.headers_len = hs->resp_headers.tail_offset; + seg[1].data = hs->resp_headers_buf; seg[1].len = msg.data.headers_len; n_segs = 2; } - else - { - msg.data.headers_offset = 0; - msg.data.headers_len = 0; - } msg.type = HTTP_MSG_REPLY; msg.code = status; @@ -266,7 +262,6 @@ hts_start_send_data (hts_session_t *hs, http_status_code_t status) ts = session_get (hs->vpp_session_index, hs->thread_index); rv = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs, 0 /* allow partial */); - vec_free (headers_buf); ASSERT (rv == (sizeof (msg) + msg.data.headers_len)); if (!msg.data.body_len) @@ -320,8 +315,7 @@ try_test_file (hts_session_t *hs, u8 *target) } } - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_add_header (&hs->resp_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); hts_start_send_data (hs, HTTP_STATUS_OK); @@ -380,9 +374,9 @@ hts_ts_rx_callback (session_t *ts) if (hs->left_recv == 0) { hs->data_len = 0; - hs->resp_headers = 0; hs->rx_buf = 0; - + http_init_headers_ctx (&hs->resp_headers, hs->resp_headers_buf, + vec_len (hs->resp_headers_buf)); /* Read the http message header */ rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); @@ -394,8 +388,7 @@ hts_ts_rx_callback (session_t *ts) } if (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST) { - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_ALLOW), + http_add_header (&hs->resp_headers, HTTP_HEADER_ALLOW, http_token_lit ("GET, POST")); hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED); goto done; diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index 38d96bbf5ac..1bcc1e85a17 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -63,32 +63,39 @@ proxy_session_side_ctx_get (proxy_worker_t *wrk, u32 ctx_index) } static_always_inline void -proxy_send_http_resp (session_t *s, http_status_code_t sc, u8 *headers_buf) +proxy_send_http_resp (session_t *s, http_status_code_t sc, + http_headers_ctx_t *headers) { http_msg_t msg; int rv; uword headers_ptr; + svm_fifo_seg_t seg[2]; + u32 n_segs = 1; ASSERT (s->thread_index == vlib_get_thread_index ()); + msg.data.headers_len = 0; + if (headers) + { + msg.data.headers_len = headers->tail_offset; + headers_ptr = pointer_to_uword (headers->buf); + seg[1].data = (u8 *) &headers_ptr; + seg[1].len = sizeof (headers_ptr); + n_segs = 2; + } msg.type = HTTP_MSG_REPLY; msg.code = sc; msg.data.type = HTTP_MSG_DATA_PTR; - msg.data.headers_len = vec_len (headers_buf); msg.data.len = msg.data.headers_len; msg.data.headers_offset = 0; msg.data.body_len = 0; msg.data.body_offset = 0; + seg[0].data = (u8 *) &msg; + seg[0].len = sizeof (msg); - headers_ptr = pointer_to_uword (headers_buf); - svm_fifo_seg_t seg[2] = { - { (u8 *) &msg, sizeof (msg) }, - { (u8 *) &headers_ptr, sizeof (headers_ptr) }, - }; - - rv = svm_fifo_enqueue_segments (s->tx_fifo, seg, msg.data.len ? 2 : 1, - 0 /* allow partial */); - ASSERT (rv == (sizeof (msg) + (msg.data.len ? sizeof (headers_ptr) : 0))); + rv = + svm_fifo_enqueue_segments (s->tx_fifo, seg, n_segs, 0 /* allow partial */); + ASSERT (rv == (sizeof (msg) + (n_segs == 2 ? sizeof (headers_ptr) : 0))); if (svm_fifo_set_event (s->tx_fifo)) session_program_tx_io_evt (s->handle, SESSION_IO_EVT_TX); @@ -872,7 +879,7 @@ active_open_send_http_resp_rpc (void *arg) session_get_from_handle (ps->ao.session_handle)); if (ao_tp == TRANSPORT_PROTO_UDP) proxy_send_http_resp (po_s, HTTP_STATUS_SWITCHING_PROTOCOLS, - pm->capsule_proto_header); + &pm->capsule_proto_header); else proxy_send_http_resp (po_s, HTTP_STATUS_OK, 0); } @@ -1469,8 +1476,6 @@ VLIB_CLI_COMMAND (proxy_create_command, static) = { clib_error_t * proxy_main_init (vlib_main_t * vm) { - http_header_t *headers = 0; - proxy_main_t *pm = &proxy_main; pm->server_client_index = ~0; pm->active_open_client_index = ~0; @@ -1478,11 +1483,12 @@ proxy_main_init (vlib_main_t * vm) pm->idle_timeout = 600; /* connect-proxy default idle timeout 10 minutes */ vec_validate (pm->client_sep, TRANSPORT_N_PROTOS - 1); - http_add_header (&headers, - http_header_name_token (HTTP_HEADER_CAPSULE_PROTOCOL), + vec_validate (pm->capsule_proto_header_buf, 10); + http_init_headers_ctx (&pm->capsule_proto_header, + pm->capsule_proto_header_buf, + vec_len (pm->capsule_proto_header_buf)); + http_add_header (&pm->capsule_proto_header, HTTP_HEADER_CAPSULE_PROTOCOL, http_token_lit (HTTP_BOOLEAN_TRUE)); - pm->capsule_proto_header = http_serialize_headers (headers); - vec_free (headers); return 0; } diff --git a/src/plugins/hs_apps/proxy.h b/src/plugins/hs_apps/proxy.h index 814bebfe6cb..f26f4bf0ea2 100644 --- a/src/plugins/hs_apps/proxy.h +++ b/src/plugins/hs_apps/proxy.h @@ -90,7 +90,8 @@ typedef struct u32 active_open_app_index; /**< active open index after attach */ u32 ckpair_index; /**< certkey pair index for tls */ - u8 *capsule_proto_header; + http_headers_ctx_t capsule_proto_header; + u8 *capsule_proto_header_buf; /* * Configuration params diff --git a/src/plugins/hs_apps/vcl/vcl_test_protos.c b/src/plugins/hs_apps/vcl/vcl_test_protos.c index da4b6997ec1..fd17c7b2c54 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_protos.c +++ b/src/plugins/hs_apps/vcl/vcl_test_protos.c @@ -15,7 +15,6 @@ #include <hs_apps/vcl/vcl_test.h> #include <http/http.h> -#include <http/http_header_names.h> #include <http/http_content_types.h> typedef enum vcl_test_http_state_ @@ -1162,7 +1161,7 @@ vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, uint32_t nbytes) { http_msg_t msg; - http_header_t *req_headers = 0; + http_headers_ctx_t req_headers; u8 *headers_buf = 0; u8 *target; vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque; @@ -1207,11 +1206,11 @@ vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, else if (PREDICT_FALSE (vcl_test_http_ctx->test_state == VCL_TEST_HTTP_IDLE)) { + vec_validate (headers_buf, 63); + http_init_headers_ctx (&req_headers, headers_buf, vec_len (headers_buf)); http_add_header ( - &req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + &req_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); - headers_buf = http_serialize_headers (req_headers); - vec_free (req_headers); memset (&msg, 0, sizeof (http_msg_t)); msg.type = HTTP_MSG_REQUEST; @@ -1223,7 +1222,7 @@ vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, /* headers */ msg.data.headers_offset = msg.data.target_path_len; - msg.data.headers_len = vec_len (headers_buf); + msg.data.headers_len = req_headers.tail_offset; /* body */ msg.data.body_offset = msg.data.headers_offset + msg.data.headers_len; @@ -1236,7 +1235,7 @@ vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, vppcom_data_segment_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, { target, strlen ((char *) target) }, { headers_buf, - vec_len (headers_buf) } }; + msg.data.headers_len } }; do { diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 666f45c7d8f..69b661d0611 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -468,14 +468,12 @@ static const char *connection_upgrade_template = "Connection: upgrade\r\n" */ static const char *http_get_request_template = "GET %s HTTP/1.1\r\n" "Host: %v\r\n" - "User-Agent: %v\r\n" - "%s"; + "User-Agent: %v\r\n"; static const char *http_post_request_template = "POST %s HTTP/1.1\r\n" "Host: %v\r\n" "User-Agent: %v\r\n" - "Content-Length: %llu\r\n" - "%s"; + "Content-Length: %llu\r\n"; static u32 http_send_data (http_conn_t *hc, u8 *data, u32 length) @@ -1368,6 +1366,76 @@ error: return HTTP_SM_ERROR; } +static void +http_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf) +{ + http_main_t *hm = &http_main; + session_t *as; + u8 *app_headers, *p, *end; + u32 *tmp; + int rv; + + as = session_get_from_handle (hc->h_pa_session_handle); + + /* read app header list */ + if (msg->data.type == HTTP_MSG_DATA_PTR) + { + uword app_headers_ptr; + rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr), + (u8 *) &app_headers_ptr); + ASSERT (rv == sizeof (app_headers_ptr)); + app_headers = uword_to_pointer (app_headers_ptr, u8 *); + } + else + { + app_headers = hm->app_header_lists[hc->c_thread_index]; + rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers); + ASSERT (rv == msg->data.headers_len); + } + + /* serialize app headers to tx_buf */ + end = app_headers + msg->data.headers_len; + while (app_headers < end) + { + /* custom header name? */ + tmp = (u32 *) app_headers; + if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT)) + { + http_custom_token_t *name, *value; + name = (http_custom_token_t *) app_headers; + u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT; + app_headers += sizeof (http_custom_token_t) + name_len; + value = (http_custom_token_t *) app_headers; + app_headers += sizeof (http_custom_token_t) + value->len; + vec_add2 (*tx_buf, p, name_len + value->len + 4); + clib_memcpy (p, name->token, name_len); + p += name_len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, value->token, value->len); + p += value->len; + *p++ = '\r'; + *p++ = '\n'; + } + else + { + http_app_header_t *header; + header = (http_app_header_t *) app_headers; + app_headers += sizeof (http_app_header_t) + header->value.len; + http_token_t name = { http_header_name_token (header->name) }; + vec_add2 (*tx_buf, p, name.len + header->value.len + 4); + clib_memcpy (p, name.base, name.len); + p += name.len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, header->value.token, header->value.len); + p += header->value.len; + *p++ = '\r'; + *p++ = '\n'; + } + } +} + static http_sm_result_t http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) { @@ -1407,6 +1475,8 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) return HTTP_SM_ERROR; } + response = hm->tx_bufs[hc->c_thread_index]; + vec_reset_length (response); /* * Add "protocol layer" headers: * - current time @@ -1414,11 +1484,12 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) * - data length */ now = clib_timebase_now (&hm->timebase); - response = format (0, http_response_template, http_status_code_str[msg.code], - /* Date */ - format_clib_timebase_time, now, - /* Server */ - hc->app_name); + response = + format (response, http_response_template, http_status_code_str[msg.code], + /* Date */ + format_clib_timebase_time, now, + /* Server */ + hc->app_name); /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a * 2xx (Successful) response to CONNECT or with a status code of 101 @@ -1449,28 +1520,10 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) if (msg.data.headers_len) { HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); - if (msg.data.type == HTTP_MSG_DATA_PTR) - { - uword app_headers_ptr; - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr), - (u8 *) &app_headers_ptr); - ASSERT (rv == sizeof (app_headers_ptr)); - vec_append (response, uword_to_pointer (app_headers_ptr, u8 *)); - } - else - { - u32 orig_len = vec_len (response); - vec_resize (response, msg.data.headers_len); - u8 *p = response + orig_len; - rv = svm_fifo_dequeue (as->tx_fifo, msg.data.headers_len, p); - ASSERT (rv == msg.data.headers_len); - } - } - else - { - /* No headers from app */ - response = format (response, "\r\n"); + http_write_app_headers (hc, &msg, &response); } + /* Add empty line after headers */ + response = format (response, "\r\n"); HTTP_DBG (3, "%v", response); sent = http_send_data (hc, response, vec_len (response)); @@ -1478,10 +1531,8 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) { clib_warning ("sending status-line and headers failed!"); sc = HTTP_STATUS_INTERNAL_ERROR; - vec_free (response); goto error; } - vec_free (response); if (msg.data.body_len) { @@ -1513,6 +1564,7 @@ error: static http_sm_result_t http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) { + http_main_t *hm = &http_main; http_msg_t msg; session_t *as; u8 *target_buff = 0, *request = 0, *target; @@ -1556,6 +1608,8 @@ http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) target = target_buff; } + request = hm->tx_bufs[hc->c_thread_index]; + vec_reset_length (request); /* currently we support only GET and POST method */ if (msg.method_type == HTTP_REQ_GET) { @@ -1569,15 +1623,13 @@ http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) * - host * - user agent */ - request = format (0, http_get_request_template, + request = format (request, http_get_request_template, /* target */ target, /* Host */ hc->host, /* User-Agent */ - hc->app_name, - /* Any headers from app? */ - msg.data.headers_len ? "" : "\r\n"); + hc->app_name); next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY; sm_result = HTTP_SM_STOP; @@ -1595,7 +1647,7 @@ http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) * - user agent * - content length */ - request = format (0, http_post_request_template, + request = format (request, http_post_request_template, /* target */ target, /* Host */ @@ -1603,9 +1655,7 @@ http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) /* User-Agent */ hc->app_name, /* Content-Length */ - msg.data.body_len, - /* Any headers from app? */ - msg.data.headers_len ? "" : "\r\n"); + msg.data.body_len); http_buffer_init (&hc->req.tx_buf, msg_to_buf_type[msg.data.type], as->tx_fifo, msg.data.body_len); @@ -1623,23 +1673,10 @@ http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) if (msg.data.headers_len) { HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); - if (msg.data.type == HTTP_MSG_DATA_PTR) - { - uword app_headers_ptr; - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr), - (u8 *) &app_headers_ptr); - ASSERT (rv == sizeof (app_headers_ptr)); - vec_append (request, uword_to_pointer (app_headers_ptr, u8 *)); - } - else - { - u32 orig_len = vec_len (request); - vec_resize (request, msg.data.headers_len); - u8 *p = request + orig_len; - rv = svm_fifo_dequeue (as->tx_fifo, msg.data.headers_len, p); - ASSERT (rv == msg.data.headers_len); - } + http_write_app_headers (hc, &msg, &request); } + /* Add empty line after headers */ + request = format (request, "\r\n"); HTTP_DBG (3, "%v", request); sent = http_send_data (hc, request, vec_len (request)); @@ -1661,7 +1698,6 @@ error: done: vec_free (target_buff); - vec_free (request); return sm_result; } @@ -2297,6 +2333,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) vec_validate (hm->wrk, num_threads - 1); vec_validate (hm->rx_bufs, num_threads - 1); vec_validate (hm->tx_bufs, num_threads - 1); + vec_validate (hm->app_header_lists, num_threads - 1); for (i = 0; i < num_threads; i++) { vec_validate (hm->rx_bufs[i], @@ -2305,6 +2342,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) vec_validate (hm->tx_bufs[i], HTTP_UDP_PAYLOAD_MAX_LEN + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD); + vec_validate (hm->app_header_lists[i], 32 << 10); } clib_timebase_init (&hm->timebase, 0 /* GMT */, CLIB_TIMEBASE_DAYLIGHT_NONE, diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 637452e0474..d61ac0b08c7 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -525,6 +525,7 @@ typedef struct http_main_ u8 **rx_bufs; u8 **tx_bufs; + u8 **app_header_lists; clib_timebase_t timebase; @@ -1135,65 +1136,72 @@ http_get_header (http_header_table_t *header_table, const char *name, return 0; } -/** - * Add header to the list. - * - * @param headers Header list. - * @param name Pointer to header's name buffer. - * @param name_len Length of the name. - * @param value Pointer to header's value buffer. - * @param value_len Length of the value. - * - * @note Headers added at protocol layer: Date, Server, Content-Length - */ +typedef struct +{ + u32 len; /**< length of the header data buffer */ + u32 tail_offset; /**< current tail in header data */ + u8 *buf; /**< start of header data */ +} http_headers_ctx_t; + +typedef struct +{ + u32 len; + u8 token[0]; +} http_custom_token_t; + +typedef struct +{ + u32 name; + http_custom_token_t value; +} http_app_header_t; + +/* Use high bit of header name length as custom header name bit. */ +#define HTTP_CUSTOM_HEADER_NAME_BIT (1 << 31) + always_inline void -http_add_header (http_header_t **headers, const char *name, uword name_len, - const char *value, uword value_len) +http_init_headers_ctx (http_headers_ctx_t *ctx, u8 *buf, u32 len) { - http_header_t *header; - vec_add2 (*headers, header, 1); - header->name.base = (char *) name; - header->name.len = name_len; - header->value.base = (char *) value; - header->value.len = value_len; + ctx->len = len; + ctx->tail_offset = 0; + ctx->buf = buf; } -/** - * Serialize the header list. - * - * @param headers Header list to serialize. - * - * @return New vector with serialized headers. - * - * The caller is always responsible to free the returned vector. - */ -always_inline u8 * -http_serialize_headers (http_header_t *headers) +always_inline void +http_add_header (http_headers_ctx_t *ctx, http_header_name_t name, + const char *value, uword value_len) { - u8 *headers_buf = 0, *dst; - u32 headers_buf_len = 2; - http_header_t *header; + http_app_header_t *header; - vec_foreach (header, headers) - headers_buf_len += header->name.len + header->value.len + 4; + ASSERT ((ctx->tail_offset + sizeof (http_app_header_t) + value_len) < + ctx->len); - vec_validate (headers_buf, headers_buf_len - 1); - dst = headers_buf; + header = (http_app_header_t *) (ctx->buf + ctx->tail_offset); + header->name = (u32) name; + header->value.len = (u32) value_len; + clib_memcpy (header->value.token, (u8 *) value, value_len); + ctx->tail_offset += sizeof (http_app_header_t) + value_len; +} - vec_foreach (header, headers) - { - clib_memcpy (dst, header->name.base, header->name.len); - dst += header->name.len; - *dst++ = ':'; - *dst++ = ' '; - clib_memcpy (dst, header->value.base, header->value.len); - dst += header->value.len; - *dst++ = '\r'; - *dst++ = '\n'; - } - *dst++ = '\r'; - *dst = '\n'; - return headers_buf; +always_inline void +http_add_custom_header (http_headers_ctx_t *ctx, const char *name, + uword name_len, const char *value, uword value_len) +{ + http_custom_token_t *token; + + ASSERT ((ctx->tail_offset + 2 * sizeof (http_custom_token_t) + name_len + + value_len) < ctx->len); + + /* name */ + token = (http_custom_token_t *) (ctx->buf + ctx->tail_offset); + token->len = (u32) name_len; + clib_memcpy (token->token, (u8 *) name, token->len); + token->len |= HTTP_CUSTOM_HEADER_NAME_BIT; + ctx->tail_offset += sizeof (http_custom_token_t) + name_len; + /* value */ + token = (http_custom_token_t *) (ctx->buf + ctx->tail_offset); + token->len = (u32) value_len; + clib_memcpy (token->token, (u8 *) value, token->len); + ctx->tail_offset += sizeof (http_custom_token_t) + value_len; } typedef enum http_uri_host_type_ diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst index 55c5afc3a2d..995e55e6f0f 100644 --- a/src/plugins/http/http_plugin.rst +++ b/src/plugins/http/http_plugin.rst @@ -16,10 +16,10 @@ Usage The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``, ``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_build_header_table``, ``http_get_header``, -``http_reset_header_table``, ``http_free_header_table``, ``http_add_header``, ``http_validate_target_syntax``, -``http_serialize_headers``, ``http_parse_authority``, ``http_serialize_authority``, ``http_parse_masque_host_port``, -``http_decap_udp_payload_datagram``, ``http_encap_udp_payload_datagram``. ``http_token_is``, ``http_token_is_case``, -``http_token_contains`` +``http_reset_header_table``, ``http_free_header_table``, ``http_init_headers_ctx``, ``http_add_header``, +``http_add_custom_header``, ``http_validate_target_syntax``, ``http_parse_authority``, ``http_serialize_authority``, +``http_parse_masque_host_port``, ``http_decap_udp_payload_datagram``, ``http_encap_udp_payload_datagram``, +``http_token_is``, ``http_token_is_case``, ``http_token_contains`` It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications. @@ -253,10 +253,9 @@ Application should set following items: * header section offset and length * body offset and length -Application could pass headers back to HTTP layer. Header list is created dynamically as vector of ``http_header_t``, -where we store only pointers to buffers (zero copy). -Well known header names are predefined. -The list is serialized just before you send buffer to HTTP layer. +Application could pass headers back to HTTP layer. Header list is created dynamically using ``http_headers_ctx_t``, which must be initialized with preallocated buffer. +Well known header names are predefined and are added using ``http_add_header``, for headers with custom names use ``http_add_custom_header``. +Header list buffer is sent buffer to HTTP layer in raw, current length is stored ``tail_offset`` member of ``http_headers_ctx_t``. .. note:: Following headers are added at protocol layer and **MUST NOT** be set by application: Date, Server, Content-Length, Connection, Upgrade @@ -268,18 +267,20 @@ Following example shows how to create headers section: #include <http/http.h> #include <http/http_header_names.h> #include <http/http_content_types.h> - http_header_t *resp_headers = 0; + http_headers_ctx_t resp_headers; u8 *headers_buf = 0; - http_add_header (resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + /* allocate buffer for response header list */ + vec_validate (headers_buf, 1023); + /* initialize header list context */ + http_init_headers_ctx (&resp_headers, headers_buf, vec_len (headers_buf)); + /* add headers to the list */ + http_add_header (&resp_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (HTTP_CONTENT_TEXT_HTML)); - http_add_header (resp_headers, - http_header_name_token (HTTP_HEADER_CACHE_CONTROL), + http_add_header (&resp_headers, HTTP_HEADER_CACHE_CONTROL, http_token_lit ("max-age=600")); - http_add_header (resp_headers, - http_header_name_token (HTTP_HEADER_LOCATION), - (const char *) redirect, vec_len (redirect)); - headers_buf = http_serialize_headers (resp_headers); + http_add_custom_header (&resp_headers, + http_token_lit ("X-Frame-Options"), + (const char *) x_frame_opt, vec_len (x_frame_opt)); The example below show how to create and send response HTTP message metadata: @@ -289,7 +290,7 @@ The example below show how to create and send response HTTP message metadata: msg.type = HTTP_MSG_REPLY; msg.code = HTTP_STATUS_MOVED msg.data.headers_offset = 0; - msg.data.headers_len = vec_len (headers_buf); + msg.data.headers_len = resp_headers.tail_offset; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.body_len = vec_len (tx_buf); msg.data.body_offset = msg.data.headers_len; @@ -298,11 +299,11 @@ The example below show how to create and send response HTTP message metadata: rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); -Next you will send your serialized headers: +Next you will send your headers: .. code-block:: C - rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf); + rv = svm_fifo_enqueue (ts->tx_fifo, msg.data.headers_len, headers_buf); ASSERT (rv == msg.data.headers_len); vec_free (headers_buf); @@ -377,13 +378,12 @@ The example below shows how to create headers section: #include <http/http.h> #include <http/http_header_names.h> #include <http/http_content_types.h> - http_header_t *req_headers = 0; + http_headers_ctx_t *req_headers; u8 *headers_buf = 0; - http_add_header (req_headers, - http_header_name_token (HTTP_HEADER_ACCEPT), + vec_validate (headers_buf, 63); + http_init_headers_ctx (&eq_headers, headers_buf, vec_len (headers_buf)); + http_add_header (req_headers, HTTP_HEADER_ACCEPT, http_content_type_token (HTTP_CONTENT_TEXT_HTML)); - headers_buf = http_serialize_headers (req_headers); - vec_free (hs->req_headers); Following example shows how to set message metadata: @@ -398,7 +398,7 @@ Following example shows how to set message metadata: msg.data.target_path_len = vec_len (target); /* custom headers */ msg.data.headers_offset = msg.data.target_path_len; - msg.data.headers_len = vec_len (headers_buf); + msg.data.headers_len = headers.tail_offset; /* no request body because we are doing GET request */ msg.data.body_len = 0; /* data type and total length */ @@ -411,7 +411,7 @@ Finally application sends everything to HTTP layer: svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, /* message metadata */ { target, vec_len (target) }, /* request target */ - { headers_buf, vec_len (headers_buf) } }; /* serialized headers */ + { headers_buf, msg.data.headers_len } }; /* headers */ rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */); vec_free (headers_buf); if (rv < 0 || rv != sizeof (msg) + msg.data.len) diff --git a/src/plugins/http_static/http_static.h b/src/plugins/http_static/http_static.h index 5e0654fae24..88b0002c971 100644 --- a/src/plugins/http_static/http_static.h +++ b/src/plugins/http_static/http_static.h @@ -53,9 +53,9 @@ typedef struct int free_data; /** File cache pool index */ u32 cache_pool_index; - /** Response header list */ - http_header_t *resp_headers; - /** Serialized headers to send */ + /** Response header ctx */ + http_headers_ctx_t resp_headers; + /** Response header buffer */ u8 *headers_buf; } hss_session_t; diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index fe5718b7e0f..7c8c65bb9bd 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -19,7 +19,6 @@ #include <sys/stat.h> #include <unistd.h> -#include <http/http_header_names.h> #include <http/http_content_types.h> /** @file static_server.c @@ -41,6 +40,8 @@ hss_session_alloc (u32 thread_index) hs->session_index = hs - hsm->sessions[thread_index]; hs->thread_index = thread_index; hs->cache_pool_index = ~0; + /* 1kB for headers should be enough for now */ + vec_validate (hs->headers_buf, 1023); return hs; } @@ -86,29 +87,17 @@ start_send_data (hss_session_t *hs, http_status_code_t status) { http_msg_t msg; session_t *ts; - u8 *headers_buf = 0; u32 n_enq; u64 to_send; int rv; ts = session_get (hs->vpp_session_index, hs->thread_index); - if (vec_len (hs->resp_headers)) - { - headers_buf = http_serialize_headers (hs->resp_headers); - vec_free (hs->resp_headers); - msg.data.headers_offset = 0; - msg.data.headers_len = vec_len (headers_buf); - } - else - { - msg.data.headers_offset = 0; - msg.data.headers_len = 0; - } - msg.type = HTTP_MSG_REPLY; msg.code = status; msg.data.body_len = hs->data_len; + msg.data.headers_offset = 0; + msg.data.headers_len = hs->resp_headers.tail_offset; msg.data.len = msg.data.body_len + msg.data.headers_len; if (msg.data.len > hss_main.use_ptr_thresh) @@ -119,7 +108,6 @@ start_send_data (hss_session_t *hs, http_status_code_t status) if (msg.data.headers_len) { - hs->headers_buf = headers_buf; uword headers = pointer_to_uword (hs->headers_buf); rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (headers), (u8 *) &headers); @@ -144,9 +132,9 @@ start_send_data (hss_session_t *hs, http_status_code_t status) if (msg.data.headers_len) { - rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf); + rv = + svm_fifo_enqueue (ts->tx_fifo, msg.data.headers_len, hs->headers_buf); ASSERT (rv == msg.data.headers_len); - vec_free (headers_buf); } if (!msg.data.body_len) @@ -187,11 +175,8 @@ hss_session_send_data (hss_url_handler_args_t *args) /* Set content type only if we have some response data */ if (hs->data_len) - { - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), - http_content_type_token (args->ct)); - } + http_add_header (&hs->resp_headers, HTTP_HEADER_CONTENT_TYPE, + http_content_type_token (args->ct)); start_send_data (hs, args->sc); } @@ -320,11 +305,8 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt, /* Set content type only if we have some response data */ if (hs->data_len) - { - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), - http_content_type_token (args.ct)); - } + http_add_header (&hs->resp_headers, HTTP_HEADER_CONTENT_TYPE, + http_content_type_token (args.ct)); start_send_data (hs, sc); @@ -401,10 +383,9 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path) vec_free (port_str); - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_LOCATION), + http_add_header (&hs->resp_headers, HTTP_HEADER_LOCATION, (const char *) redirect, vec_len (redirect)); - hs->data = redirect; /* TODO: find better way */ + vec_free (redirect); hs->data_len = 0; hs->free_data = 1; @@ -479,16 +460,15 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt, /* Set following headers only for happy path: * Content-Type * Cache-Control max-age + * Last-Modified */ type = content_type_from_request (target); - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_add_header (&hs->resp_headers, HTTP_HEADER_CONTENT_TYPE, http_content_type_token (type)); - http_add_header ( - &hs->resp_headers, http_header_name_token (HTTP_HEADER_CACHE_CONTROL), - (const char *) hsm->max_age_formatted, vec_len (hsm->max_age_formatted)); - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_LAST_MODIFIED), + http_add_header (&hs->resp_headers, HTTP_HEADER_CACHE_CONTROL, + (const char *) hsm->max_age_formatted, + vec_len (hsm->max_age_formatted)); + http_add_header (&hs->resp_headers, HTTP_HEADER_LAST_MODIFIED, (const char *) last_modified, vec_len (last_modified)); done: @@ -529,8 +509,8 @@ hss_ts_rx_callback (session_t *ts) if (hs->free_data) vec_free (hs->data); hs->data = 0; - hs->resp_headers = 0; - vec_free (hs->headers_buf); + http_init_headers_ctx (&hs->resp_headers, hs->headers_buf, + vec_len (hs->headers_buf)); /* Read the http message header */ rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg); @@ -539,8 +519,7 @@ hss_ts_rx_callback (session_t *ts) if (msg.type != HTTP_MSG_REQUEST || (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST)) { - http_add_header (&hs->resp_headers, - http_header_name_token (HTTP_HEADER_ALLOW), + http_add_header (&hs->resp_headers, HTTP_HEADER_ALLOW, http_token_lit ("GET, POST")); start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED); goto done; |