mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-28 02:11:43 +00:00
Add HttpParser and UrlParser C++ wrappers for http_parser.
This commit is contained in:
164
wpiutil/src/main/native/cpp/HttpParser.cpp
Normal file
164
wpiutil/src/main/native/cpp/HttpParser.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "wpi/HttpParser.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
uint32_t HttpParser::GetParserVersion() {
|
||||
return static_cast<uint32_t>(http_parser_version());
|
||||
}
|
||||
|
||||
HttpParser::HttpParser(Type type) {
|
||||
http_parser_init(&m_parser,
|
||||
static_cast<http_parser_type>(static_cast<int>(type)));
|
||||
m_parser.data = this;
|
||||
|
||||
http_parser_settings_init(&m_settings);
|
||||
|
||||
// Unlike the underlying http_parser library, we don't perform callbacks
|
||||
// (other than body) with partial data; instead we buffer and call the user
|
||||
// callback only when the data is complete.
|
||||
|
||||
// on_message_begin: initialize our state, call user callback
|
||||
m_settings.on_message_begin = [](http_parser* p) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
self.m_urlBuf.clear();
|
||||
self.m_state = kStart;
|
||||
self.messageBegin();
|
||||
return self.m_aborted;
|
||||
};
|
||||
|
||||
// on_url: collect into buffer
|
||||
m_settings.on_url = [](http_parser* p, const char* at, size_t length) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
// append to buffer
|
||||
if ((self.m_urlBuf.size() + length) > self.m_maxLength) return 1;
|
||||
self.m_urlBuf += StringRef{at, length};
|
||||
self.m_state = kUrl;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// on_status: collect into buffer, call user URL callback
|
||||
m_settings.on_status = [](http_parser* p, const char* at,
|
||||
size_t length) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
// use valueBuf for the status
|
||||
if ((self.m_valueBuf.size() + length) > self.m_maxLength) return 1;
|
||||
self.m_valueBuf += StringRef{at, length};
|
||||
self.m_state = kStatus;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// on_header_field: collect into buffer, call user header/status callback
|
||||
m_settings.on_header_field = [](http_parser* p, const char* at,
|
||||
size_t length) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
|
||||
// once we're in header, we know the URL is complete
|
||||
if (self.m_state == kUrl) {
|
||||
self.url(self.m_urlBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
// once we're in header, we know the status is complete
|
||||
if (self.m_state == kStatus) {
|
||||
self.status(self.m_valueBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
// if we previously were in value state, that means we finished a header
|
||||
if (self.m_state == kValue) {
|
||||
self.header(self.m_fieldBuf, self.m_valueBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
// clear field and value when we enter this state
|
||||
if (self.m_state != kField) {
|
||||
self.m_state = kField;
|
||||
self.m_fieldBuf.clear();
|
||||
self.m_valueBuf.clear();
|
||||
}
|
||||
|
||||
// append data to field buffer
|
||||
if ((self.m_fieldBuf.size() + length) > self.m_maxLength) return 1;
|
||||
self.m_fieldBuf += StringRef{at, length};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// on_header_field: collect into buffer
|
||||
m_settings.on_header_value = [](http_parser* p, const char* at,
|
||||
size_t length) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
|
||||
// if we weren't previously in value state, clear the buffer
|
||||
if (self.m_state != kValue) {
|
||||
self.m_state = kValue;
|
||||
self.m_valueBuf.clear();
|
||||
}
|
||||
|
||||
// append data to value buffer
|
||||
if ((self.m_valueBuf.size() + length) > self.m_maxLength) return 1;
|
||||
self.m_valueBuf += StringRef{at, length};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// on_headers_complete: call user status/header/complete callback
|
||||
m_settings.on_headers_complete = [](http_parser* p) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
|
||||
// if we previously were in url state, that means we finished the url
|
||||
if (self.m_state == kUrl) {
|
||||
self.url(self.m_urlBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
// if we previously were in status state, that means we finished the status
|
||||
if (self.m_state == kStatus) {
|
||||
self.status(self.m_valueBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
// if we previously were in value state, that means we finished a header
|
||||
if (self.m_state == kValue) {
|
||||
self.header(self.m_fieldBuf, self.m_valueBuf);
|
||||
if (self.m_aborted) return 1;
|
||||
}
|
||||
|
||||
self.headersComplete(self.ShouldKeepAlive());
|
||||
return self.m_aborted;
|
||||
};
|
||||
|
||||
// on_body: call user callback
|
||||
m_settings.on_body = [](http_parser* p, const char* at,
|
||||
size_t length) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
self.body(StringRef{at, length}, self.IsBodyFinal());
|
||||
return self.m_aborted;
|
||||
};
|
||||
|
||||
// on_message_complete: call user callback
|
||||
m_settings.on_message_complete = [](http_parser* p) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
self.messageComplete(self.ShouldKeepAlive());
|
||||
return self.m_aborted;
|
||||
};
|
||||
|
||||
// on_chunk_header: call user callback
|
||||
m_settings.on_chunk_header = [](http_parser* p) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
self.chunkHeader(p->content_length);
|
||||
return self.m_aborted;
|
||||
};
|
||||
|
||||
// on_chunk_complete: call user callback
|
||||
m_settings.on_chunk_complete = [](http_parser* p) -> int {
|
||||
auto& self = *static_cast<HttpParser*>(p->data);
|
||||
self.chunkComplete();
|
||||
return self.m_aborted;
|
||||
};
|
||||
}
|
||||
216
wpiutil/src/main/native/include/wpi/HttpParser.h
Normal file
216
wpiutil/src/main/native/include/wpi/HttpParser.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef WPIUTIL_WPI_HTTPPARSER_H_
|
||||
#define WPIUTIL_WPI_HTTPPARSER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "wpi/Signal.h"
|
||||
#include "wpi/SmallString.h"
|
||||
#include "wpi/StringRef.h"
|
||||
#include "wpi/http_parser.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
/**
|
||||
* HTTP protocol parser. Performs incremental parsing with callbacks for each
|
||||
* part of the HTTP protocol. As this is incremental, it's suitable for use
|
||||
* with event based frameworks that provide arbitrary chunks of data.
|
||||
*/
|
||||
class HttpParser {
|
||||
public:
|
||||
enum Type {
|
||||
kRequest = HTTP_REQUEST,
|
||||
kResponse = HTTP_RESPONSE,
|
||||
kBoth = HTTP_BOTH
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
*/
|
||||
static uint32_t GetParserVersion();
|
||||
|
||||
explicit HttpParser(Type type);
|
||||
|
||||
/**
|
||||
* Set the maximum accepted length for URLs, field names, and field values.
|
||||
* The default is 1024.
|
||||
* @param len maximum length
|
||||
*/
|
||||
void SetMaxLength(size_t len) { m_maxLength = len; }
|
||||
|
||||
/**
|
||||
* Executes the parser. An empty input is treated as EOF.
|
||||
* @param in input data
|
||||
* @return Number of parsed bytes.
|
||||
*/
|
||||
size_t Execute(StringRef in) {
|
||||
return http_parser_execute(&m_parser, &m_settings, in.data(), in.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP major version.
|
||||
*/
|
||||
unsigned int GetMajor() const { return m_parser.http_major; }
|
||||
|
||||
/**
|
||||
* Get HTTP minor version.
|
||||
*/
|
||||
unsigned int GetMinor() const { return m_parser.http_minor; }
|
||||
|
||||
/**
|
||||
* Get HTTP status code. Valid only on responses. Valid in and after
|
||||
* the OnStatus() callback has been called.
|
||||
*/
|
||||
unsigned int GetStatusCode() const { return m_parser.status_code; }
|
||||
|
||||
/**
|
||||
* Get HTTP method. Valid only on requests.
|
||||
*/
|
||||
http_method GetMethod() const {
|
||||
return static_cast<http_method>(m_parser.method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an error occurred.
|
||||
* @return False if no error.
|
||||
*/
|
||||
bool HasError() const { return m_parser.http_errno != HPE_OK; }
|
||||
|
||||
/**
|
||||
* Get error number.
|
||||
*/
|
||||
http_errno GetError() const {
|
||||
return static_cast<http_errno>(m_parser.http_errno);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the parse. Call this from a callback handler to indicate an error.
|
||||
* This will result in GetError() returning one of the callback-related
|
||||
* errors (e.g. HPE_CB_message_begin).
|
||||
*/
|
||||
void Abort() { m_aborted = true; }
|
||||
|
||||
/**
|
||||
* Determine if an upgrade header was present and the parser has exited
|
||||
* because of that. Should be checked when Execute() returns in addition to
|
||||
* checking GetError().
|
||||
* @return True if upgrade header, false otherwise.
|
||||
*/
|
||||
bool IsUpgrade() const { return m_parser.upgrade; }
|
||||
|
||||
/**
|
||||
* If this returns false in the headersComplete or messageComplete
|
||||
* callback, then this should be the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
bool ShouldKeepAlive() const { return http_should_keep_alive(&m_parser); }
|
||||
|
||||
/**
|
||||
* Pause the parser.
|
||||
* @param paused True to pause, false to unpause.
|
||||
*/
|
||||
void Pause(bool paused) { http_parser_pause(&m_parser, paused); }
|
||||
|
||||
/**
|
||||
* Checks if this is the final chunk of the body.
|
||||
*/
|
||||
bool IsBodyFinal() const { return http_body_is_final(&m_parser); }
|
||||
|
||||
/**
|
||||
* Get URL. Valid in and after the url callback has been called.
|
||||
*/
|
||||
StringRef GetUrl() const { return m_urlBuf; }
|
||||
|
||||
/**
|
||||
* Message begin callback.
|
||||
*/
|
||||
sig::Signal<> messageBegin;
|
||||
|
||||
/**
|
||||
* URL callback.
|
||||
*
|
||||
* The parameter to the callback is the complete URL string.
|
||||
*/
|
||||
sig::Signal<StringRef> url;
|
||||
|
||||
/**
|
||||
* Status callback.
|
||||
*
|
||||
* The parameter to the callback is the complete status string.
|
||||
* GetStatusCode() can be used to get the numeric status code.
|
||||
*/
|
||||
sig::Signal<StringRef> status;
|
||||
|
||||
/**
|
||||
* Header field callback.
|
||||
*
|
||||
* The parameters to the callback are the field name and field value.
|
||||
*/
|
||||
sig::Signal<StringRef, StringRef> header;
|
||||
|
||||
/**
|
||||
* Headers complete callback.
|
||||
*
|
||||
* The parameter to the callback is whether the connection should be kept
|
||||
* alive. If this is false, then this should be the last message on the
|
||||
* connection. If you are the server, respond with the "Connection: close"
|
||||
* header. If you are the client, close the connection.
|
||||
*/
|
||||
sig::Signal<bool> headersComplete;
|
||||
|
||||
/**
|
||||
* Body data callback.
|
||||
*
|
||||
* The parameters to the callback is the data chunk and whether this is the
|
||||
* final chunk of data in the message. Note this callback will be called
|
||||
* multiple times arbitrarily (e.g. it's possible that it may be called with
|
||||
* just a few characters at a time).
|
||||
*/
|
||||
sig::Signal<StringRef, bool> body;
|
||||
|
||||
/**
|
||||
* Headers complete callback.
|
||||
*
|
||||
* The parameter to the callback is whether the connection should be kept
|
||||
* alive. If this is false, then this should be the last message on the
|
||||
* connection. If you are the server, respond with the "Connection: close"
|
||||
* header. If you are the client, close the connection.
|
||||
*/
|
||||
sig::Signal<bool> messageComplete;
|
||||
|
||||
/**
|
||||
* Chunk header callback.
|
||||
*
|
||||
* The parameter to the callback is the chunk size.
|
||||
*/
|
||||
sig::Signal<uint64_t> chunkHeader;
|
||||
|
||||
/**
|
||||
* Chunk complete callback.
|
||||
*/
|
||||
sig::Signal<> chunkComplete;
|
||||
|
||||
private:
|
||||
http_parser m_parser;
|
||||
http_parser_settings m_settings;
|
||||
|
||||
size_t m_maxLength = 1024;
|
||||
enum { kStart, kUrl, kStatus, kField, kValue } m_state = kStart;
|
||||
SmallString<128> m_urlBuf;
|
||||
SmallString<32> m_fieldBuf;
|
||||
SmallString<128> m_valueBuf;
|
||||
|
||||
bool m_aborted = false;
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPIUTIL_WPI_HTTPPARSER_H_
|
||||
92
wpiutil/src/main/native/include/wpi/UrlParser.h
Normal file
92
wpiutil/src/main/native/include/wpi/UrlParser.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef WPIUTIL_WPI_URLPARSER_H_
|
||||
#define WPIUTIL_WPI_URLPARSER_H_
|
||||
|
||||
#include "wpi/StringRef.h"
|
||||
#include "wpi/http_parser.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
/**
|
||||
* Parses a URL into its constiuent components.
|
||||
* `schema://userinfo@host:port/the/path?query#fragment`
|
||||
*/
|
||||
class UrlParser {
|
||||
public:
|
||||
/**
|
||||
* Parse a URL.
|
||||
* @param in input
|
||||
* @param isConnect
|
||||
*/
|
||||
UrlParser(StringRef in, bool isConnect) {
|
||||
m_data = in;
|
||||
http_parser_url_init(&m_url);
|
||||
m_error = http_parser_parse_url(in.data(), in.size(), isConnect, &m_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the URL is valid (e.g. the parse was successful).
|
||||
*/
|
||||
bool IsValid() const { return !m_error; }
|
||||
|
||||
bool HasSchema() const { return (m_url.field_set & UF_SCHEMA) != 0; }
|
||||
|
||||
bool HasHost() const { return (m_url.field_set & UF_HOST) != 0; }
|
||||
|
||||
bool HasPort() const { return (m_url.field_set & UF_PORT) != 0; }
|
||||
|
||||
bool HasPath() const { return (m_url.field_set & UF_PATH) != 0; }
|
||||
|
||||
bool HasQuery() const { return (m_url.field_set & UF_QUERY) != 0; }
|
||||
|
||||
bool HasFragment() const { return (m_url.field_set & UF_FRAGMENT) != 0; }
|
||||
|
||||
bool HasUserInfo() const { return (m_url.field_set & UF_USERINFO) != 0; }
|
||||
|
||||
StringRef GetSchema() const {
|
||||
return m_data.substr(m_url.field_data[UF_SCHEMA].off,
|
||||
m_url.field_data[UF_SCHEMA].len);
|
||||
}
|
||||
|
||||
StringRef GetHost() const {
|
||||
return m_data.substr(m_url.field_data[UF_HOST].off,
|
||||
m_url.field_data[UF_HOST].len);
|
||||
}
|
||||
|
||||
unsigned int GetPort() const { return m_url.port; }
|
||||
|
||||
StringRef GetPath() const {
|
||||
return m_data.substr(m_url.field_data[UF_PATH].off,
|
||||
m_url.field_data[UF_PATH].len);
|
||||
}
|
||||
|
||||
StringRef GetQuery() const {
|
||||
return m_data.substr(m_url.field_data[UF_QUERY].off,
|
||||
m_url.field_data[UF_QUERY].len);
|
||||
}
|
||||
|
||||
StringRef GetFragment() const {
|
||||
return m_data.substr(m_url.field_data[UF_FRAGMENT].off,
|
||||
m_url.field_data[UF_FRAGMENT].len);
|
||||
}
|
||||
|
||||
StringRef GetUserInfo() const {
|
||||
return m_data.substr(m_url.field_data[UF_USERINFO].off,
|
||||
m_url.field_data[UF_USERINFO].len);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_error;
|
||||
StringRef m_data;
|
||||
http_parser_url m_url;
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPIUTIL_WPI_URLPARSER_H_
|
||||
193
wpiutil/src/test/native/cpp/HttpParserTest.cpp
Normal file
193
wpiutil/src/test/native/cpp/HttpParserTest.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "wpi/HttpParser.h" // NOLINT(build/include_order)
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
TEST(HttpParserTest, UrlMethodHeadersComplete) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.url.connect([&](StringRef path) {
|
||||
ASSERT_EQ(path, "/foo/bar");
|
||||
ASSERT_EQ(p.GetUrl(), "/foo/bar");
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET /foo");
|
||||
p.Execute("/bar");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute(" HTTP/1.1\r\n\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_EQ(p.GetUrl(), "/foo/bar");
|
||||
ASSERT_EQ(p.GetMethod(), HTTP_GET);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, UrlMethodHeader) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.url.connect([&](StringRef path) {
|
||||
ASSERT_EQ(path, "/foo/bar");
|
||||
ASSERT_EQ(p.GetUrl(), "/foo/bar");
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET /foo");
|
||||
p.Execute("/bar");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute(" HTTP/1.1\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("F");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_EQ(p.GetUrl(), "/foo/bar");
|
||||
ASSERT_EQ(p.GetMethod(), HTTP_GET);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, StatusHeadersComplete) {
|
||||
HttpParser p{HttpParser::kResponse};
|
||||
int callbacks = 0;
|
||||
p.status.connect([&](StringRef status) {
|
||||
ASSERT_EQ(status, "OK");
|
||||
ASSERT_EQ(p.GetStatusCode(), 200u);
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("HTTP/1.1 200");
|
||||
p.Execute(" OK");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_EQ(p.GetStatusCode(), 200u);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, StatusHeader) {
|
||||
HttpParser p{HttpParser::kResponse};
|
||||
int callbacks = 0;
|
||||
p.status.connect([&](StringRef status) {
|
||||
ASSERT_EQ(status, "OK");
|
||||
ASSERT_EQ(p.GetStatusCode(), 200u);
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("HTTP/1.1 200");
|
||||
p.Execute(" OK\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("F");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_EQ(p.GetStatusCode(), 200u);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeaderFieldComplete) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.header.connect([&](StringRef name, StringRef value) {
|
||||
ASSERT_EQ(name, "Foo");
|
||||
ASSERT_EQ(value, "Bar");
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.1\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Fo");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("o: ");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Bar");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeaderFieldNext) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.header.connect([&](StringRef name, StringRef value) {
|
||||
ASSERT_EQ(name, "Foo");
|
||||
ASSERT_EQ(value, "Bar");
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.1\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Fo");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("o: ");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Bar");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("F");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeadersComplete) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.headersComplete.connect([&](bool keepAlive) {
|
||||
ASSERT_EQ(keepAlive, false);
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.0\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeadersCompleteHTTP11) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.headersComplete.connect([&](bool keepAlive) {
|
||||
ASSERT_EQ(keepAlive, true);
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.1\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeadersCompleteKeepAlive) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.headersComplete.connect([&](bool keepAlive) {
|
||||
ASSERT_EQ(keepAlive, true);
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.0\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Connection: Keep-Alive\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
TEST(HttpParserTest, HeadersCompleteUpgrade) {
|
||||
HttpParser p{HttpParser::kRequest};
|
||||
int callbacks = 0;
|
||||
p.headersComplete.connect([&](bool) {
|
||||
ASSERT_TRUE(p.IsUpgrade());
|
||||
++callbacks;
|
||||
});
|
||||
p.Execute("GET / HTTP/1.0\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("Connection: Upgrade\r\n");
|
||||
p.Execute("Upgrade: websocket\r\n");
|
||||
ASSERT_EQ(callbacks, 0);
|
||||
p.Execute("\r\n");
|
||||
ASSERT_EQ(callbacks, 1);
|
||||
ASSERT_FALSE(p.HasError());
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
Reference in New Issue
Block a user