diff options
| author | Garrett D'Amore <garrett@damore.org> | 2017-12-27 16:32:33 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2017-12-27 16:32:33 -0800 |
| commit | 5ff3d3d3ab82be4b2171e06f1def9cdddaef4115 (patch) | |
| tree | 028fff3d0dbcbec741406541931029c106bae465 /src | |
| parent | eb1f8db4ed87867f0f08afba79253e3981db9c88 (diff) | |
| download | nng-5ff3d3d3ab82be4b2171e06f1def9cdddaef4115.tar.gz nng-5ff3d3d3ab82be4b2171e06f1def9cdddaef4115.tar.bz2 nng-5ff3d3d3ab82be4b2171e06f1def9cdddaef4115.zip | |
fixes #180 add websocket header properties
Diffstat (limited to 'src')
| -rw-r--r-- | src/supplemental/http/http.h | 16 | ||||
| -rw-r--r-- | src/supplemental/http/http_msg.c | 26 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.c | 105 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.h | 2 | ||||
| -rw-r--r-- | src/transport/ws/websocket.c | 206 | ||||
| -rw-r--r-- | src/transport/ws/websocket.h | 50 |
6 files changed, 348 insertions, 57 deletions
diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h index b28845ec..3ecce4c8 100644 --- a/src/supplemental/http/http.h +++ b/src/supplemental/http/http.h @@ -45,7 +45,8 @@ extern const char *nni_http_req_get_header(nni_http_req *, const char *); extern const char *nni_http_req_get_version(nni_http_req *); extern const char *nni_http_req_get_uri(nni_http_req *); extern const char *nni_http_req_get_method(nni_http_req *); -extern int nni_http_req_parse(nni_http_req *, void *, size_t, size_t *); +extern int nni_http_req_parse(nni_http_req *, void *, size_t, size_t *); +extern char *nni_http_req_headers(nni_http_req *); extern int nni_http_res_init(nni_http_res **); extern void nni_http_res_fini(nni_http_res *); @@ -60,12 +61,13 @@ extern const char *nni_http_res_get_header(nni_http_res *, const char *); extern const char *nni_http_res_get_version(nni_http_res *); extern const char *nni_http_res_get_reason(nni_http_res *); extern int nni_http_res_get_status(nni_http_res *); -extern int nni_http_res_parse(nni_http_res *, void *, size_t, size_t *); -extern int nni_http_res_set_data(nni_http_res *, const void *, size_t); -extern int nni_http_res_copy_data(nni_http_res *, const void *, size_t); -extern int nni_http_res_alloc_data(nni_http_res *, size_t); -extern void nni_http_res_get_data(nni_http_res *, void **, size_t *); -extern int nni_http_res_init_error(nni_http_res **, uint16_t); +extern int nni_http_res_parse(nni_http_res *, void *, size_t, size_t *); +extern int nni_http_res_set_data(nni_http_res *, const void *, size_t); +extern int nni_http_res_copy_data(nni_http_res *, const void *, size_t); +extern int nni_http_res_alloc_data(nni_http_res *, size_t); +extern void nni_http_res_get_data(nni_http_res *, void **, size_t *); +extern int nni_http_res_init_error(nni_http_res **, uint16_t); +extern char *nni_http_res_headers(nni_http_res *); // HTTP status codes. This list is not exhaustive. enum { NNI_HTTP_STATUS_CONTINUE = 100, diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c index 2e245868..8b3c6a7c 100644 --- a/src/supplemental/http/http_msg.c +++ b/src/supplemental/http/http_msg.c @@ -520,6 +520,32 @@ http_res_prepare(nni_http_res *res) return (rv); } +char * +nni_http_req_headers(nni_http_req *req) +{ + char * s; + size_t len; + + len = http_sprintf_headers(NULL, 0, &req->hdrs) + 1; + if ((s = nni_alloc(len)) != NULL) { + http_sprintf_headers(s, len, &req->hdrs); + } + return (s); +} + +char * +nni_http_res_headers(nni_http_res *res) +{ + char * s; + size_t len; + + len = http_sprintf_headers(NULL, 0, &res->hdrs) + 1; + if ((s = nni_alloc(len)) != NULL) { + http_sprintf_headers(s, len, &res->hdrs); + } + return (s); +} + int nni_http_req_get_buf(nni_http_req *req, void **data, size_t *szp) { diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index dfcac3e7..53e4af8c 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -23,10 +23,16 @@ typedef struct ws_frame ws_frame; typedef struct ws_msg ws_msg; +typedef struct ws_header { + nni_list_node node; + char * name; + char * value; +} ws_header; + struct nni_ws { - int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN nni_list_node node; nni_reap_item reap; + int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN bool closed; bool ready; bool wclose; @@ -42,6 +48,8 @@ struct nni_ws { nni_http * http; nni_http_req *req; nni_http_res *res; + char * reqhdrs; + char * reshdrs; size_t maxframe; size_t fragsize; }; @@ -64,6 +72,7 @@ struct nni_ws_listener { nni_http_handler handler; nni_ws_listen_hook hookfn; void * hookarg; + nni_list headers; // response headers }; // The dialer tracks user aios in two lists. The first list is for aios @@ -91,6 +100,7 @@ struct nni_ws_dialer { bool started; bool closed; nng_sockaddr sa; + nni_list headers; // request headers }; typedef enum ws_type { @@ -724,7 +734,6 @@ ws_start_read(nni_ws *ws) nni_aio_finish_error(wm->aio, NNG_ENOMEM); } ws_msg_fini(wm); - // XXX: NOW WHAT? return; } @@ -1028,6 +1037,28 @@ nni_ws_request(nni_ws *ws) return (ws->req); } +const char * +nni_ws_request_headers(nni_ws *ws) +{ + nni_mtx_lock(&ws->mtx); + if (ws->reqhdrs == NULL) { + ws->reqhdrs = nni_http_req_headers(ws->req); + } + nni_mtx_unlock(&ws->mtx); + return (ws->reqhdrs); +} + +const char * +nni_ws_response_headers(nni_ws *ws) +{ + nni_mtx_lock(&ws->mtx); + if (ws->reshdrs == NULL) { + ws->reshdrs = nni_http_res_headers(ws->res); + } + nni_mtx_unlock(&ws->mtx); + return (ws->reshdrs); +} + static void ws_fini(void *arg) { @@ -1075,6 +1106,8 @@ ws_fini(void *arg) nni_http_res_fini(ws->res); } + nni_strfree(ws->reqhdrs); + nni_strfree(ws->reshdrs); nni_http_fini(ws->http); nni_aio_fini(ws->rxaio); nni_aio_fini(ws->txaio); @@ -1261,12 +1294,20 @@ ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) void nni_ws_listener_fini(nni_ws_listener *l) { + ws_header *hdr; + nni_mtx_fini(&l->mtx); nni_strfree(l->url); nni_strfree(l->proto); nni_strfree(l->host); nni_strfree(l->serv); nni_strfree(l->path); + while ((hdr = nni_list_first(&l->headers)) != NULL) { + nni_list_remove(&l->headers, hdr); + nni_strfree(hdr->name); + nni_strfree(hdr->value); + NNI_FREE_STRUCT(hdr); + } NNI_FREE_STRUCT(l); } @@ -1718,6 +1759,7 @@ ws_conn_cb(void *arg) uint8_t raw[16]; char wskey[25]; nni_ws * ws; + ws_header * hdr; nni_mtx_lock(&d->mtx); uaio = nni_list_first(&d->conaios); @@ -1776,6 +1818,13 @@ ws_conn_cb(void *arg) ((rv = SETH("Sec-WebSocket-Protocol", d->proto)) != 0)) { goto err; } + + NNI_LIST_FOREACH (&d->headers, hdr) { + if ((rv = SETH(hdr->name, hdr->value)) != 0) { + goto err; + } + } + #undef SETH if ((rv = ws_init(&ws, http, req, NULL)) != 0) { @@ -1807,6 +1856,8 @@ err: void nni_ws_dialer_fini(nni_ws_dialer *d) { + ws_header *hdr; + nni_aio_fini(d->conaio); nni_strfree(d->proto); nni_strfree(d->addr); @@ -1815,6 +1866,12 @@ nni_ws_dialer_fini(nni_ws_dialer *d) nni_strfree(d->serv); nni_strfree(d->path); nni_strfree(d->qinfo); + while ((hdr = nni_list_first(&d->headers)) != NULL) { + nni_list_remove(&d->headers, hdr); + nni_strfree(hdr->name); + nni_strfree(hdr->value); + NNI_FREE_STRUCT(hdr); + } if (d->client) { nni_http_client_fini(d->client); } @@ -1832,6 +1889,7 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *url) if ((d = NNI_ALLOC_STRUCT(d)) == NULL) { return (NNG_ENOMEM); } + NNI_LIST_INIT(&d->headers, ws_header, node); nni_mtx_init(&d->mtx); nni_aio_list_init(&d->conaios); nni_aio_list_init(&d->httpaios); @@ -1877,7 +1935,6 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *url) void nni_ws_dialer_close(nni_ws_dialer *d) { - // XXX: what to do here? nni_mtx_lock(&d->mtx); if (d->closed) { nni_mtx_unlock(&d->mtx); @@ -1944,7 +2001,47 @@ nni_ws_dialer_dial(nni_ws_dialer *d, nni_aio *aio) nni_mtx_unlock(&d->mtx); } -extern int nni_ws_dialer_header(nni_ws_dialer *, const char *, const char *); +static int +ws_set_header(nni_list *l, const char *n, const char *v) +{ + ws_header *hdr; + char * nv; + + if ((nv = nni_strdup(v)) == NULL) { + return (NNG_ENOMEM); + } + + NNI_LIST_FOREACH (l, hdr) { + if (strcasecmp(hdr->name, n) == 0) { + nni_strfree(hdr->value); + hdr->value = nv; + return (0); + } + } + + if ((hdr = NNI_ALLOC_STRUCT(hdr)) == NULL) { + nni_strfree(nv); + return (NNG_ENOMEM); + } + if ((hdr->name = strdup(n)) == NULL) { + nni_strfree(nv); + NNI_FREE_STRUCT(hdr); + return (NNG_ENOMEM); + } + hdr->value = nv; + nni_list_append(l, hdr); + return (0); +} + +int +nni_ws_dialer_header(nni_ws_dialer *d, const char *n, const char *v) +{ + int rv; + nni_mtx_lock(&d->mtx); + rv = ws_set_header(&d->headers, n, v); + nni_mtx_unlock(&d->mtx); + return (rv); +} // Dialer does not get a hook chance, as it can examine the request // and reply after dial is done; this is not a 3-way handshake, so diff --git a/src/supplemental/websocket/websocket.h b/src/supplemental/websocket/websocket.h index 3c3ce085..95147a9f 100644 --- a/src/supplemental/websocket/websocket.h +++ b/src/supplemental/websocket/websocket.h @@ -59,6 +59,8 @@ extern int nni_ws_peer_addr(nni_ws *, nni_sockaddr *); extern void nni_ws_close(nni_ws *); extern void nni_ws_close_error(nni_ws *, uint16_t); extern void nni_ws_fini(nni_ws *); +extern const char * nni_ws_response_headers(nni_ws *); +extern const char * nni_ws_request_headers(nni_ws *); // The implementation will send periodic PINGs, and respond with PONGs. diff --git a/src/transport/ws/websocket.c b/src/transport/ws/websocket.c index 178338f9..c6ce9b25 100644 --- a/src/transport/ws/websocket.c +++ b/src/transport/ws/websocket.c @@ -14,11 +14,20 @@ #include <string.h> #include "core/nng_impl.h" +#include "supplemental/http/http.h" #include "supplemental/websocket/websocket.h" +#include "websocket.h" + typedef struct ws_ep ws_ep; typedef struct ws_pipe ws_pipe; +typedef struct ws_hdr { + nni_list_node node; + char * name; + char * value; +} ws_hdr; + struct ws_ep { int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN char * addr; @@ -32,6 +41,7 @@ struct ws_ep { nni_aio * accaio; nni_ws_listener *listener; nni_ws_dialer * dialer; + nni_list headers; // to send, res or req }; struct ws_pipe { @@ -242,9 +252,32 @@ ws_pipe_start(void *arg, nni_aio *aio) // Servers use the HTTP server framework, and a request methodology. static int +ws_hook(void *arg, nni_http_req *req, nni_http_res *res) +{ + ws_ep * ep = arg; + ws_hdr *h; + // Eventually we'll want user customizable hooks. + // For now we just set the headers we want. + + nni_mtx_lock(&ep->mtx); + NNI_LIST_FOREACH (&ep->headers, h) { + int rv; + rv = nni_http_req_set_header(req, h->name, h->value); + if (rv != 0) { + nni_mtx_unlock(&ep->mtx); + return (rv); + } + } + nni_mtx_unlock(&ep->mtx); + return (0); +} + +static int ws_ep_bind(void *arg) { ws_ep *ep = arg; + + nni_ws_listener_hook(ep->listener, ws_hook, ep); return (nni_ws_listener_listen(ep->listener)); } @@ -284,8 +317,9 @@ ws_ep_accept(void *arg, nni_aio *aio) static void ws_ep_connect(void *arg, nni_aio *aio) { - ws_ep *ep = arg; - int rv; + ws_ep * ep = arg; + int rv; + ws_hdr *h; nni_mtx_lock(&ep->mtx); NNI_ASSERT(nni_list_empty(&ep->aios)); @@ -297,6 +331,15 @@ ws_ep_connect(void *arg, nni_aio *aio) return; } + NNI_LIST_FOREACH (&ep->headers, h) { + rv = nni_ws_dialer_header(ep->dialer, h->name, h->value); + if (rv != 0) { + nni_aio_finish_error(aio, rv); + nni_mtx_unlock(&ep->mtx); + return; + } + } + nni_list_append(&ep->aios, aio); nni_ws_dialer_dial(ep->dialer, ep->connaio); nni_mtx_unlock(&ep->mtx); @@ -313,6 +356,117 @@ ws_ep_setopt_recvmaxsz(void *arg, const void *v, size_t sz) } static int +ws_ep_setopt_headers(ws_ep *ep, const void *v, size_t sz) +{ + // XXX: check that the string is well formed. + char * dupstr; + size_t duplen; + char * name; + char * value; + char * nl; + nni_list l; + ws_hdr * h; + int rv; + + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); + } + if (ep == NULL) { + return (0); + } + + NNI_LIST_INIT(&l, ws_hdr, node); + if ((dupstr = nni_strdup(v)) == NULL) { + return (NNG_ENOMEM); + } + duplen = strlen(dupstr) + 1; // so we can free it later + name = dupstr; + for (;;) { + if ((value = strchr(name, ':')) == NULL) { + // Note that this also means that if + // a bare word is present, we ignore it. + break; + } + *value = '\0'; + value++; + while (*value == ' ') { + // Skip leading whitespace. Not strictly + // necessary, but still a good idea. + value++; + } + nl = value; + // Find the end of the line -- should be CRLF, but can + // also be unterminated or just LF if user + while ((*nl != '\0') && (*nl != '\r') && (*nl != '\n')) { + nl++; + } + while ((*nl == '\r') || (*nl == '\n')) { + *nl = '\0'; + nl++; + } + + if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { + rv = NNG_ENOMEM; + goto done; + } + nni_list_append(&l, h); + if (((h->name = nni_strdup(name)) == NULL) || + ((h->value = nni_strdup(value)) == NULL)) { + rv = NNG_ENOMEM; + goto done; + } + + name = nl; + } + + nni_mtx_lock(&ep->mtx); + while ((h = nni_list_first(&ep->headers)) != NULL) { + nni_list_remove(&ep->headers, h); + nni_strfree(h->name); + nni_strfree(h->value); + NNI_FREE_STRUCT(h); + } + while ((h = nni_list_first(&l)) != NULL) { + nni_list_remove(&l, h); + nni_list_append(&ep->headers, h); + } + nni_mtx_unlock(&ep->mtx); + rv = 0; + +done: + while ((h = nni_list_first(&l)) != NULL) { + nni_list_remove(&l, h); + nni_strfree(h->name); + nni_strfree(h->value); + NNI_FREE_STRUCT(h); + } + nni_free(dupstr, duplen); + return (rv); +} + +static int +ws_ep_setopt_reqhdrs(void *arg, const void *v, size_t sz) +{ + ws_ep *ep = arg; + + if (ep->mode == NNI_EP_MODE_LISTEN) { + return (NNG_EREADONLY); + } + return (ws_ep_setopt_headers(ep, v, sz)); +} + +static int +ws_ep_setopt_reshdrs(void *arg, const void *v, size_t sz) +{ + ws_ep *ep = arg; + + if (ep->mode == NNI_EP_MODE_DIAL) { + return (NNG_EREADONLY); + } + return (ws_ep_setopt_headers(ep, v, sz)); +} + +static int ws_ep_getopt_recvmaxsz(void *arg, void *v, size_t *szp) { ws_ep *ep = arg; @@ -347,11 +501,38 @@ ws_pipe_getopt_remaddr(void *arg, void *v, size_t *szp) return (rv); } +static int +ws_pipe_getopt_reshdrs(void *arg, void *v, size_t *szp) +{ + ws_pipe * p = arg; + const char *s; + + if ((s = nni_ws_response_headers(p->ws)) == NULL) { + return (NNG_ENOMEM); + } + return (nni_getopt_str(s, v, szp)); +} + +static int +ws_pipe_getopt_reqhdrs(void *arg, void *v, size_t *szp) +{ + ws_pipe * p = arg; + const char *s; + + if ((s = nni_ws_request_headers(p->ws)) == NULL) { + return (NNG_ENOMEM); + } + return (nni_getopt_str(s, v, szp)); +} + static nni_tran_pipe_option ws_pipe_options[] = { // clang-format off { NNG_OPT_LOCADDR, ws_pipe_getopt_locaddr }, { NNG_OPT_REMADDR, ws_pipe_getopt_remaddr }, + { NNG_OPT_WS_REQUEST_HEADERS, ws_pipe_getopt_reqhdrs }, + { NNG_OPT_WS_RESPONSE_HEADERS, ws_pipe_getopt_reshdrs }, + // clang-format on // terminate list @@ -374,6 +555,17 @@ static nni_tran_ep_option ws_ep_options[] = { .eo_getopt = ws_ep_getopt_recvmaxsz, .eo_setopt = ws_ep_setopt_recvmaxsz, }, + { + .eo_name = NNG_OPT_WS_REQUEST_HEADERS, + .eo_getopt = NULL, + .eo_setopt = ws_ep_setopt_reqhdrs, + }, + { + .eo_name = NNG_OPT_WS_RESPONSE_HEADERS, + .eo_getopt = NULL, + .eo_setopt = ws_ep_setopt_reshdrs, + }, + // terminate list { NULL, NULL, NULL }, }; @@ -381,7 +573,8 @@ static nni_tran_ep_option ws_ep_options[] = { static void ws_ep_fini(void *arg) { - ws_ep *ep = arg; + ws_ep * ep = arg; + ws_hdr *hdr; nni_aio_stop(ep->accaio); nni_aio_stop(ep->connaio); @@ -393,6 +586,12 @@ ws_ep_fini(void *arg) if (ep->dialer != NULL) { nni_ws_dialer_fini(ep->dialer); } + while ((hdr = nni_list_first(&ep->headers)) != NULL) { + nni_list_remove(&ep->headers, hdr); + nni_strfree(hdr->name); + nni_strfree(hdr->value); + NNI_FREE_STRUCT(hdr); + } nni_strfree(ep->addr); nni_strfree(ep->protoname); nni_mtx_fini(&ep->mtx); @@ -493,6 +692,7 @@ ws_ep_init(void **epp, const char *url, nni_sock *sock, int mode) } nni_mtx_init(&ep->mtx); + NNI_LIST_INIT(&ep->headers, ws_hdr, node); // List of pipes (server only). nni_aio_list_init(&ep->aios); diff --git a/src/transport/ws/websocket.h b/src/transport/ws/websocket.h index 1beb6156..2e86aaf0 100644 --- a/src/transport/ws/websocket.h +++ b/src/transport/ws/websocket.h @@ -11,52 +11,16 @@ #ifndef NNG_TRANSPORT_WS_WEBSOCKET_H #define NNG_TRANSPORT_WS_WEBSOCKET_H -// TLS transport. This is used for communication via TLS v1.2 over TCP/IP. +// WebSocket transport. This is used for communication via WebSocket. NNG_DECL int nng_ws_register(void); -// TLS options. Note that these can only be set *before* the endpoint is -// started. Once started, it is no longer possible to alter the TLS -// configuration. +// NNG_OPT_TLS_REQUEST_HEADERS is a string containing the +// request headers, formatted as CRLF terminated lines. +#define NNG_OPT_WS_REQUEST_HEADERS "ws:request-headers" -// NNG_OPT_TLS_CA_CERT is a string with one or more X.509 certificates, -// representing the entire CA chain. The content may be either PEM or DER -// encoded. -#define NNG_OPT_TLS_CA_CERT "tls:ca-cert" - -// NNG_OPT_TLS_CRL is a PEM encoded CRL (revocation list). Multiple lists -// may be loaded by using this option multiple times. -#define NNG_OPT_TLS_CRL "tls:crl" - -// NNG_OPT_TLS_CERT is used to specify our own certificate. At present -// only one certificate may be supplied. (In the future it may be -// possible to call this multiple times, for servers that select different -// certificates depending upon client capabilities.) -#define NNG_OPT_TLS_CERT "tls:cert" - -// NNG_OPT_TLS_PRIVATE_KEY is used to specify the private key used -// with the given certificate. This should be called after setting -// the certificate. The private key may be in PEM or DER format. -// If in PEM encoded, a terminating ZERO byte should be included. -#define NNG_OPT_TLS_PRIVATE_KEY "tls:private-key" - -// NNG_OPT_TLS_PRIVATE_KEY_PASSWORD is used to specify a password -// used for the private key. The value is an ASCIIZ string. -#define NNG_OPT_TLS_PRIVATE_KEY_PASSWORD "tls:private-key-password" - -// NNG_OPT_TLS_AUTH_MODE is an integer indicating whether our -// peer should be verified or not. It is required on clients/dialers, -// and off on servers/listeners, by default. -#define NNG_OPT_TLS_AUTH_MODE "tls:auth-mode" - -extern int nng_tls_auth_mode_required; -extern int nng_tls_auth_mode_none; -extern int nng_tls_auth_mode_optional; - -// NNG_OPT_TLS_AUTH_VERIFIED is a boolean that can be read on pipes, -// indicating whether the peer certificate is verified. -#define NNG_OPT_TLS_AUTH_VERIFIED "tls:auth-verified" - -// XXX: TBD: Ciphersuite selection and reporting. Session reuse? +// NNG_OPT_TLS_RESPONSE_HEADERS is a string containing the +// response headers, formatted as CRLF terminated lines. +#define NNG_OPT_WS_RESPONSE_HEADERS "ws:response-headers" #endif // NNG_TRANSPORT_WS_WEBSOCKET_H |
