about summary refs log tree commit homepage
path: root/http_parser.rl
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2012-01-13 23:10:56 +0000
committerEric Wong <normalperson@yhbt.net>2012-01-13 23:28:26 +0000
commitbbd34a4c405e055e2f6a9055a3d07c116c478a4b (patch)
treea9a93f78a6874c7f15493d69b8f29ee68ed5b954 /http_parser.rl
parent50a65dbea059ed17a3b1d0a2821c787ee6e8f772 (diff)
downloadcmogstored-bbd34a4c405e055e2f6a9055a3d07c116c478a4b.tar.gz
This only needs to understand enough HTTP to support MogileFS.
We should also be able to avoid the need for heap memory allocation
in all common cases.
Diffstat (limited to 'http_parser.rl')
-rw-r--r--http_parser.rl125
1 files changed, 125 insertions, 0 deletions
diff --git a/http_parser.rl b/http_parser.rl
new file mode 100644
index 0000000..202b4a8
--- /dev/null
+++ b/http_parser.rl
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
+ * License: GPLv3 or later (see COPYING for details)
+ */
+#include "cmogstored.h"
+#include "http_util.h"
+
+%%{
+        machine http_parser;
+
+        LWS = (' ' | '\t');
+        LF = '\n' > { http->line_end = fpc - buf; };
+        eor = LWS*'\r'LF;
+        CTL = (cntrl | 127);
+        mog_path = '/'[a-zA-Z0-9/\.\-]{0,36}; # only stuff MogileFS will use
+        header_name = [a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9];
+        header_value = (any -- CTL)*;
+        sep = LWS+|LWS*"\r\n"LWS+;
+
+        ignored_header := header_name ':' sep header_value eor @ {
+                fgoto more_headers;
+        };
+
+        GET = "GET "> { http->http_method = MOG_HTTP_METHOD_GET; };
+        HEAD = "HEAD "> { http->http_method = MOG_HTTP_METHOD_HEAD; };
+        PUT = "PUT "> { http->http_method = MOG_HTTP_METHOD_PUT; };
+        DELETE = "DELETE "> { http->http_method = MOG_HTTP_METHOD_DELETE; };
+        MKCOL = "MKCOL "> { http->http_method = MOG_HTTP_METHOD_MKCOL; };
+
+        # no HTTP/0.9 for now, sorry (not :P)
+        req_line = (HEAD|GET|PUT|DELETE|MKCOL)
+                (mog_path) > { http->path_tip = to_u8(fpc - buf); }
+                # TODO: maybe folks use query string/fragments for logging...
+                (" HTTP/1.") > { http->path_end = to_u8(fpc - buf); }
+                ('0'|'1'> { http->persistent = 1; }) '\r'LF;
+
+        content_length = "Content-Length:"i sep
+                (digit+) > { http->tmp_tip = to_u16(fpc - buf); }
+                eor > {
+                        uint16_t tmp_end = fpc - buf;
+                        char *path = buf + http->tmp_tip;
+                        size_t pathlen = tmp_end - http->tmp_tip;
+                        char *end;
+
+                        path[pathlen] = 0;
+                        errno = 0;
+                        http->content_len = (off_t)strtoll(path, &end, 10);
+                        if (errno == ERANGE) { /* overflow */
+                                http_set_status(http, 400); /* Bad Request */
+                                fbreak;
+                        }
+                        assert(*end == 0 && "HTTP parser broke");
+                };
+        transfer_encoding_chunked = "Transfer-Encoding:"i sep
+                "chunked"i eor > { http->content_len = -1; };
+        trailer = "Trailer:"i sep
+                header_value eor > { http->has_trailer = 1; };
+        connection = "Connection:"i sep
+                (("close"i @ { http->persistent = 0; }) |
+                 ("keep-alive"i @ { http->persistent = 1; })) eor;
+        header_line =
+                ( content_length |
+                  transfer_encoding_chunked |
+                  trailer |
+                  connection ) $!
+                {
+                        assert(http->line_end > 0 &&
+                               "no previous request/header line");
+                        assert(buf[http->line_end] == '\n' &&
+                               "bad http->line_end");
+                        p = buf + http->line_end + 1;
+                        assert(p <= pe && "overflow");
+                        fgoto ignored_header;
+                };
+        headers = header_line* '\r''\n' > { really_done = 1; fbreak; };
+        more_headers := headers;
+        main := req_line headers;
+}%%
+
+%% write data;
+
+void mog_http_init(struct mog_http *http, struct mog_svc *svc)
+{
+        int cs;
+
+        memset(http, 0, sizeof(struct mog_http));
+        %% write init;
+        http->cs = cs;
+        http->svc = svc;
+}
+
+enum mog_parser_state
+mog_http_parse(struct mog_http *http, char *buf, size_t len)
+{
+        char *p, *pe, *eof = NULL;
+        int cs = http->cs;
+        int really_done = 0;
+        size_t off = http->offset;
+
+        assert(http->wbuf == NULL && "unwritten data in buffer");
+        assert(off <= len && "http offset past end of buffer");
+
+        p = buf + off;
+        pe = buf + len;
+
+        assert((void *)(pe - p) == (void *)(len - off) &&
+               "pointers aren't same distance");
+
+        %% write exec;
+
+        if (really_done)
+                cs = http_parser_first_final;
+
+        http->cs = cs;
+        http->offset = p - buf;
+
+        if (cs == http_parser_error)
+                return MOG_PARSER_ERROR;
+
+        assert(p <= pe && "buffer overflow after http parse");
+        assert(http->offset <= len && "offset longer than len");
+
+        if (http->cs == http_parser_first_final) return MOG_PARSER_DONE;
+        return MOG_PARSER_CONTINUE;
+}