diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-01-24 17:38:16 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-02-01 16:11:38 -0800 |
| commit | 3dae30ed5e543dc73fc993334ef56b9b157b9b3c (patch) | |
| tree | d7e294b5d544aa18e8fc8749abfe605a05fa4bd7 /src/supplemental/http/http_msg.c | |
| parent | 5914e40c2ff7fcf346c90705785f3fb7650a9fdc (diff) | |
| download | nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.tar.gz nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.tar.bz2 nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.zip | |
fixes #173 Define public HTTP server API
This introduces enough of the HTTP API to support fully server
applications, including creation of websocket style protocols,
pluggable handlers, and so forth.
We have also introduced scatter/gather I/O (rudimentary) for
aios, and made other enhancements to the AIO framework. The
internals of the AIOs themselves are now fully private, and we
have eliminated the aio->a_addr member, with plans to remove the
pipe and possibly message members as well.
A few other minor issues were found and fixed as well.
The HTTP API includes request, response, and connection objects,
which can be used with both servers and clients. It also defines
the HTTP server and handler objects, which support server applications.
Support for client applications will require a client object to be
exposed, and that should be happening shortly.
None of this is "documented" yet, bug again, we will follow up shortly.
Diffstat (limited to 'src/supplemental/http/http_msg.c')
| -rw-r--r-- | src/supplemental/http/http_msg.c | 318 |
1 files changed, 179 insertions, 139 deletions
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c index da8c70f3..81c1b453 100644 --- a/src/supplemental/http/http_msg.c +++ b/src/supplemental/http/http_msg.c @@ -27,14 +27,14 @@ typedef struct http_header { nni_list_node node; } http_header; -struct nni_http_entity { +typedef struct nni_http_entity { char * data; size_t size; // allocated/expected size size_t len; // current length bool own; // if true, data is "ours", and should be freed -}; +} nni_http_entity; -struct nni_http_req { +struct nng_http_req { nni_list hdrs; nni_http_entity data; char * meth; @@ -42,23 +42,27 @@ struct nni_http_req { char * vers; char * buf; size_t bufsz; + bool parsed; }; -struct nni_http_res { +struct nng_http_res { nni_list hdrs; nni_http_entity data; - int code; + uint16_t code; char * rsn; char * vers; char * buf; size_t bufsz; + bool parsed; }; static int http_set_string(char **strp, const char *val) { char *news; - if ((news = nni_strdup(val)) == NULL) { + if (val == NULL) { + news = NULL; + } else if ((news = nni_strdup(val)) == NULL) { return (NNG_ENOMEM); } nni_strfree(*strp); @@ -114,6 +118,8 @@ nni_http_res_reset(nni_http_res *res) http_entity_reset(&res->data); nni_strfree(res->rsn); nni_strfree(res->vers); + res->vers = NULL; + res->rsn = NULL; res->code = 0; if (res->bufsz) { res->buf[0] = '\0'; @@ -121,7 +127,7 @@ nni_http_res_reset(nni_http_res *res) } void -nni_http_req_fini(nni_http_req *req) +nni_http_req_free(nni_http_req *req) { nni_http_req_reset(req); if (req->bufsz) { @@ -131,7 +137,7 @@ nni_http_req_fini(nni_http_req *req) } void -nni_http_res_fini(nni_http_res *res) +nni_http_res_free(nni_http_res *res) { nni_http_res_reset(res); if (res->bufsz) { @@ -157,13 +163,13 @@ http_del_header(nni_list *hdrs, const char *key) } int -nni_req_del_header(nni_http_req *req, const char *key) +nni_http_req_del_header(nni_http_req *req, const char *key) { return (http_del_header(&req->hdrs, key)); } int -nni_res_del_header(nni_http_res *res, const char *key) +nni_http_res_del_header(nni_http_res *res, const char *key) { return (http_del_header(&res->hdrs, key)); } @@ -398,12 +404,6 @@ nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size) return (0); } -int -nni_http_res_alloc_data(nni_http_res *res, size_t size) -{ - return (http_entity_alloc_data(&res->data, size)); -} - static int http_parse_header(nni_list *hdrs, void *line) { @@ -501,11 +501,12 @@ static int http_req_prepare(nni_http_req *req) { int rv; - if ((req->uri == NULL) || (req->meth == NULL)) { + if (req->uri == NULL) { return (NNG_EINVAL); } rv = http_asprintf(&req->buf, &req->bufsz, &req->hdrs, "%s %s %s\r\n", - req->meth, req->uri, req->vers != NULL ? req->vers : "HTTP/1.1"); + req->meth != NULL ? req->meth : "GET", req->uri, + req->vers != NULL ? req->vers : "HTTP/1.1"); return (rv); } @@ -514,8 +515,8 @@ http_res_prepare(nni_http_res *res) { int rv; rv = http_asprintf(&res->buf, &res->bufsz, &res->hdrs, "%s %d %s\r\n", - res->vers != NULL ? res->vers : "HTTP/1.1", res->code, - res->rsn != NULL ? res->rsn : "Unknown Error"); + nni_http_res_get_version(res), nni_http_res_get_status(res), + nni_http_res_get_reason(res)); return (rv); } @@ -572,7 +573,7 @@ nni_http_res_get_buf(nni_http_res *res, void **data, size_t *szp) } int -nni_http_req_init(nni_http_req **reqp) +nni_http_req_alloc(nni_http_req **reqp, const nni_url *url) { nni_http_req *req; if ((req = NNI_ALLOC_STRUCT(req)) == NULL) { @@ -587,12 +588,33 @@ nni_http_req_init(nni_http_req **reqp) req->vers = NULL; req->meth = NULL; req->uri = NULL; - *reqp = req; + if (url != NULL) { + const char *host; + int rv; + if ((req->uri = nni_strdup(url->u_rawpath)) == NULL) { + NNI_FREE_STRUCT(req); + return (NNG_ENOMEM); + } + + // Add a Host: header since we know that from the URL. Also, + // only include the :port portion if it isn't the default port. + if (strcmp(nni_url_default_port(url->u_scheme), url->u_port) == + 0) { + host = url->u_hostname; + } else { + host = url->u_host; + } + if ((rv = nni_http_req_add_header(req, "Host", host)) != 0) { + nni_http_req_free(req); + return (rv); + } + } + *reqp = req; return (0); } int -nni_http_res_init(nni_http_res **resp) +nni_http_res_alloc(nni_http_res **resp) { nni_http_res *res; if ((res = NNI_ALLOC_STRUCT(res)) == NULL) { @@ -614,7 +636,7 @@ nni_http_res_init(nni_http_res **resp) const char * nni_http_req_get_method(nni_http_req *req) { - return (req->meth != NULL ? req->meth : ""); + return (req->meth != NULL ? req->meth : "GET"); } const char * @@ -626,24 +648,30 @@ nni_http_req_get_uri(nni_http_req *req) const char * nni_http_req_get_version(nni_http_req *req) { - return (req->vers != NULL ? req->vers : ""); + return (req->vers != NULL ? req->vers : "HTTP/1.1"); } const char * nni_http_res_get_version(nni_http_res *res) { - return (res->vers != NULL ? res->vers : ""); + return (res->vers != NULL ? res->vers : "HTTP/1.1"); } int nni_http_req_set_version(nni_http_req *req, const char *vers) { + if (strcmp(vers, "HTTP/1.1") == 0) { + vers = NULL; + } return (http_set_string(&req->vers, vers)); } int nni_http_res_set_version(nni_http_res *res, const char *vers) { + if (strcmp(vers, "HTTP/1.1") == 0) { + vers = NULL; + } return (http_set_string(&res->vers, vers)); } @@ -656,31 +684,25 @@ nni_http_req_set_uri(nni_http_req *req, const char *uri) int nni_http_req_set_method(nni_http_req *req, const char *meth) { + if (strcmp(meth, "GET") == 0) { + meth = NULL; + } return (http_set_string(&req->meth, meth)); } int -nni_http_res_set_status(nni_http_res *res, int status, const char *reason) +nni_http_res_set_status(nni_http_res *res, uint16_t status) { - int rv; - if ((rv = http_set_string(&res->rsn, reason)) == 0) { - res->code = status; - } - return (rv); + res->code = status; + return (0); } -int +uint16_t nni_http_res_get_status(nni_http_res *res) { return (res->code); } -const char * -nni_http_res_get_reason(nni_http_res *res) -{ - return (res->rsn != NULL ? res->rsn : ""); -} - static int http_scan_line(void *vbuf, size_t n, size_t *lenp) { @@ -740,6 +762,7 @@ http_req_parse_line(nni_http_req *req, void *line) ((rv = nni_http_req_set_version(req, version)) != 0)) { return (rv); } + req->parsed = true; return (0); } @@ -770,10 +793,12 @@ http_res_parse_line(nni_http_res *res, uint8_t *line) return (NNG_EPROTO); } - if (((rv = nni_http_res_set_status(res, status, reason)) != 0) || - ((rv = nni_http_res_set_version(res, version)) != 0)) { + if (((rv = nni_http_res_set_status(res, (uint16_t) status)) != 0) || + ((rv = nni_http_res_set_version(res, version)) != 0) || + ((rv = nni_http_res_set_reason(res, reason)) != 0)) { return (rv); } + res->parsed = true; return (0); } @@ -806,7 +831,7 @@ nni_http_req_parse(nni_http_req *req, void *buf, size_t n, size_t *lenp) break; } - if (req->vers != NULL) { + if (req->parsed) { rv = http_parse_header(&req->hdrs, line); } else { rv = http_req_parse_line(req, line); @@ -843,7 +868,7 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp) break; } - if (res->vers != NULL) { + if (res->parsed) { rv = http_parse_header(&res->hdrs, line); } else { rv = http_res_parse_line(res, line); @@ -858,105 +883,121 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp) return (rv); } +static struct { + uint16_t code; + const char *mesg; +} http_status[] = { + // 200, listed first because most likely + { NNG_HTTP_STATUS_OK, "OK" }, + + // 100 series -- informational + { NNG_HTTP_STATUS_CONTINUE, "Continue" }, + { NNG_HTTP_STATUS_SWITCHING, "Swithching Protocols" }, + { NNG_HTTP_STATUS_PROCESSING, "Processing" }, + + // 200 series -- successful + { NNG_HTTP_STATUS_CREATED, "Created" }, + { NNG_HTTP_STATUS_ACCEPTED, "Accepted" }, + { NNG_HTTP_STATUS_NOT_AUTHORITATIVE, "Not Authoritative" }, + { NNG_HTTP_STATUS_NO_CONTENT, "No Content" }, + { NNG_HTTP_STATUS_RESET_CONTENT, "Reset Content" }, + { NNG_HTTP_STATUS_PARTIAL_CONTENT, "Partial Content" }, + + // 300 series -- redirection + { NNG_HTTP_STATUS_MULTIPLE_CHOICES, "Multiple Choices" }, + { NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY, "Moved Permanently" }, + { NNG_HTTP_STATUS_FOUND, "Found" }, + { NNG_HTTP_STATUS_SEE_OTHER, "See Other" }, + { NNG_HTTP_STATUS_NOT_MODIFIED, "Not Modified" }, + { NNG_HTTP_STATUS_USE_PROXY, "Use Proxy" }, + { NNG_HTTP_STATUS_TEMPORARY_REDIRECT, "Temporary Redirect" }, + + // 400 series -- client errors + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Request" }, + { NNG_HTTP_STATUS_UNAUTHORIZED, "Unauthorized" }, + { NNG_HTTP_STATUS_PAYMENT_REQUIRED, "Payment Required" }, + { NNG_HTTP_STATUS_FORBIDDEN, "Forbidden" }, + { NNG_HTTP_STATUS_NOT_FOUND, "Not Found" }, + { NNG_HTTP_STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed" }, + { NNG_HTTP_STATUS_NOT_ACCEPTABLE, "Not Acceptable" }, + { NNG_HTTP_STATUS_PROXY_AUTH_REQUIRED, + "Proxy Authentication Required" }, + { NNG_HTTP_STATUS_REQUEST_TIMEOUT, "Request Timeout" }, + { NNG_HTTP_STATUS_CONFLICT, "Conflict" }, + { NNG_HTTP_STATUS_GONE, "Gone" }, + { NNG_HTTP_STATUS_LENGTH_REQUIRED, "Length Required" }, + { NNG_HTTP_STATUS_PRECONDITION_FAILED, "Precondition Failed" }, + { NNG_HTTP_STATUS_ENTITY_TOO_LONG, "Request Entity Too Long" }, + { NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" }, + { NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE, + "Requested Range Not Satisfiable" }, + { NNG_HTTP_STATUS_EXPECTATION_FAILED, "Expectation Failed" }, + { NNG_HTTP_STATUS_TEAPOT, "I Am A Teapot" }, + { NNG_HTTP_STATUS_LOCKED, "Locked" }, + { NNG_HTTP_STATUS_FAILED_DEPENDENCY, "Failed Dependency" }, + { NNG_HTTP_STATUS_UPGRADE_REQUIRED, "Upgrade Required" }, + { NNG_HTTP_STATUS_PRECONDITION_REQUIRED, "Precondition Required" }, + { NNG_HTTP_STATUS_TOO_MANY_REQUESTS, "Too Many Requests" }, + { NNG_HTTP_STATUS_HEADERS_TOO_LARGE, "Headers Too Large" }, + { NNG_HTTP_STATUS_UNAVAIL_LEGAL_REASONS, + "Unavailable For Legal Reasons" }, + + // 500 series -- server errors + { NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR, "Internal Server Error" }, + { NNG_HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented" }, + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Gateway" }, + { NNG_HTTP_STATUS_SERVICE_UNAVAILABLE, "Service Unavailable" }, + { NNG_HTTP_STATUS_GATEWAY_TIMEOUT, "Gateway Timeout" }, + { NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP, + "HTTP Version Not Supported" }, + { NNG_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates" }, + { NNG_HTTP_STATUS_INSUFFICIENT_STORAGE, "Insufficient Storage" }, + { NNG_HTTP_STATUS_LOOP_DETECTED, "Loop Detected" }, + { NNG_HTTP_STATUS_NOT_EXTENDED, "Not Extended" }, + { NNG_HTTP_STATUS_NETWORK_AUTH_REQUIRED, + "Network Authentication Required" }, + + // Terminator + { 0, NULL }, +}; + +const char * +http_reason(uint16_t code) +{ + for (int i = 0; http_status[i].code != 0; i++) { + if (http_status[i].code == code) { + return (http_status[i].mesg); + } + } + return ("Unknown HTTP Status"); +} + +const char * +nni_http_res_get_reason(nni_http_res *res) +{ + return (res->rsn ? res->rsn : http_reason(res->code)); +} + int -nni_http_res_init_error(nni_http_res **resp, uint16_t err) +nni_http_res_set_reason(nni_http_res *res, const char *reason) { - char * rsn; - char rsnbuf[80]; - char html[1024]; + if (strcmp(reason, http_reason(res->code)) == 0) { + reason = NULL; + } + return (http_set_string(&res->rsn, reason)); +} + +int +nni_http_res_alloc_error(nni_http_res **resp, uint16_t err) +{ + char html[512]; int rv; nni_http_res *res; - if ((rv = nni_http_res_init(&res)) != 0) { + if ((rv = nni_http_res_alloc(&res)) != 0) { return (rv); } - // Note that it is expected that redirect URIs will update the - // payload to reflect the target location. - switch (err) { - case NNI_HTTP_STATUS_STATUS_MOVED_PERMANENTLY: - rsn = "Moved Permanently"; - break; - case NNI_HTTP_STATUS_MULTIPLE_CHOICES: - rsn = "Multiple Choices"; - break; - case NNI_HTTP_STATUS_FOUND: - rsn = "Found"; - break; - case NNI_HTTP_STATUS_SEE_OTHER: - rsn = "See Other"; - break; - case NNI_HTTP_STATUS_TEMPORARY_REDIRECT: - rsn = "Temporary Redirect"; - break; - case NNI_HTTP_STATUS_BAD_REQUEST: - rsn = "Bad Request"; - break; - case NNI_HTTP_STATUS_UNAUTHORIZED: - rsn = "Unauthorized"; - break; - case NNI_HTTP_STATUS_PAYMENT_REQUIRED: - rsn = "Payment Required"; - break; - case NNI_HTTP_STATUS_NOT_FOUND: - rsn = "Not Found"; - break; - case NNI_HTTP_STATUS_METHOD_NOT_ALLOWED: - // Caller must also supply an Allow: header - rsn = "Method Not Allowed"; - break; - case NNI_HTTP_STATUS_NOT_ACCEPTABLE: - rsn = "Not Acceptable"; - break; - case NNI_HTTP_STATUS_REQUEST_TIMEOUT: - rsn = "Request Timeout"; - break; - case NNI_HTTP_STATUS_CONFLICT: - rsn = "Conflict"; - break; - case NNI_HTTP_STATUS_GONE: - rsn = "Gone"; - break; - case NNI_HTTP_STATUS_LENGTH_REQUIRED: - rsn = "Length Required"; - break; - case NNI_HTTP_STATUS_PAYLOAD_TOO_LARGE: - rsn = "Payload Too Large"; - break; - case NNI_HTTP_STATUS_FORBIDDEN: - rsn = "Forbidden"; - break; - case NNI_HTTP_STATUS_URI_TOO_LONG: - rsn = "URI Too Long"; - break; - case NNI_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: - rsn = "Unsupported Media Type"; - break; - case NNI_HTTP_STATUS_EXPECTATION_FAILED: - rsn = "Expectation Failed"; - break; - case NNI_HTTP_STATUS_UPGRADE_REQUIRED: - // Caller must add "Upgrade:" header. - rsn = "Upgrade Required"; - break; - case NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR: - rsn = "Internal Server Error"; - break; - case NNI_HTTP_STATUS_HTTP_VERSION_NOT_SUPP: - rsn = "HTTP version not supported"; - break; - case NNI_HTTP_STATUS_NOT_IMPLEMENTED: - rsn = "Not Implemented"; - break; - case NNI_HTTP_STATUS_SERVICE_UNAVAILABLE: - rsn = "Service Unavailable"; - break; - default: - snprintf(rsnbuf, sizeof(rsnbuf), "HTTP error code %d", err); - rsn = rsnbuf; - break; - } - // very simple builtin error page (void) snprintf(html, sizeof(html), "<head><title>%d %s</title></head>" @@ -967,14 +1008,13 @@ nni_http_res_init_error(nni_http_res **resp, uint16_t err) "<p align=\"center\">" "<span style=\"font-size: 24px; font-family: Arial, sans serif;\">" "%s</span></p></body>", - err, rsn, err, rsn); + err, http_reason(err), err, http_reason(err)); - if (((rv = nni_http_res_set_status(res, err, rsn)) != 0) || - ((rv = nni_http_res_set_version(res, "HTTP/1.1")) != 0) || - ((rv = nni_http_res_set_header( + res->code = err; + if (((rv = nni_http_res_set_header( res, "Content-Type", "text/html; charset=UTF-8")) != 0) || ((rv = nni_http_res_copy_data(res, html, strlen(html))) != 0)) { - nni_http_res_fini(res); + nni_http_res_free(res); } else { *resp = res; } |
