Add wpi::HttpServerConnection and an example of its use (#1281)

This commit is contained in:
Peter Johnson
2018-08-20 13:11:39 -07:00
committed by GitHub
parent 7b95c5341a
commit 9e37ee13de
4 changed files with 362 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
/*----------------------------------------------------------------------------*/
/* 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/HttpServerConnection.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/raw_uv_ostream.h"
using namespace wpi;
HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
: m_stream(*stream) {
// process HTTP messages
m_request.messageComplete.connect([this](bool keepAlive) {
m_keepAlive = keepAlive;
ProcessRequest();
});
// pass incoming data to HTTP parser
stream->data.connect([this](uv::Buffer& buf, size_t size) {
m_request.Execute(StringRef{buf.base, size});
if (m_request.HasError()) {
// could not parse; just close the connection
m_stream.Close();
}
});
// close when remote side closes
stream->end.connect([h = stream.get()] { h->Close(); });
// start reading
stream->StartRead();
}
void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
os << "Server: WebServer/1.0\r\n"
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
"post-check=0, max-age=0\r\n"
"Pragma: no-cache\r\n"
"Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
}
void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
const Twine& codeText,
const Twine& contentType,
uint64_t contentLength,
const Twine& extra) {
os << "HTTP/" << m_request.GetMajor() << '.' << m_request.GetMinor() << ' '
<< code << ' ' << codeText << "\r\n";
if (contentLength == 0) m_keepAlive = false;
if (!m_keepAlive) os << "Connection: close\r\n";
BuildCommonHeaders(os);
os << "Content-Type: " << contentType << "\r\n";
if (contentLength != 0) os << "Content-Length: " << contentLength << "\r\n";
os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
SmallString<128> extraBuf;
StringRef extraStr = extra.toStringRef(extraBuf);
if (!extraStr.empty()) os << extraStr << "\r\n";
os << "\r\n"; // header ends with a blank line
}
void HttpServerConnection::SendData(ArrayRef<uv::Buffer> bufs,
bool closeAfter) {
m_stream.Write(bufs, [ closeAfter, stream = &m_stream ](
MutableArrayRef<uv::Buffer> bufs, uv::Error) {
for (auto&& buf : bufs) buf.Deallocate();
if (closeAfter) stream->Close();
});
}
void HttpServerConnection::SendResponse(int code, const Twine& codeText,
const Twine& contentType,
StringRef content,
const Twine& extraHeader) {
SmallVector<uv::Buffer, 4> toSend;
raw_uv_ostream os{toSend, 4096};
BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
os << content;
// close after write completes if we aren't keeping alive
SendData(os.bufs(), !m_keepAlive);
}
void HttpServerConnection::SendError(int code, const Twine& message) {
StringRef codeText, extra, baseMessage;
switch (code) {
case 401:
codeText = "Unauthorized";
extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
baseMessage = "401: Not Authenticated!";
break;
case 404:
codeText = "Not Found";
baseMessage = "404: Not Found!";
break;
case 500:
codeText = "Internal Server Error";
baseMessage = "500: Internal Server Error!";
break;
case 400:
codeText = "Bad Request";
baseMessage = "400: Not Found!";
break;
case 403:
codeText = "Forbidden";
baseMessage = "403: Forbidden!";
break;
case 503:
codeText = "Service Unavailable";
baseMessage = "503: Service Unavailable";
break;
default:
code = 501;
codeText = "Not Implemented";
baseMessage = "501: Not Implemented!";
break;
}
SmallString<256> content = baseMessage;
content += "\r\n";
message.toVector(content);
SendResponse(code, codeText, "text/plain", content, extra);
}

View File

@@ -0,0 +1,123 @@
/*----------------------------------------------------------------------------*/
/* 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_HTTPSERVERCONNECTION_H_
#define WPIUTIL_WPI_HTTPSERVERCONNECTION_H_
#include <memory>
#include "wpi/ArrayRef.h"
#include "wpi/HttpParser.h"
#include "wpi/StringRef.h"
#include "wpi/Twine.h"
#include "wpi/uv/Stream.h"
namespace wpi {
class raw_ostream;
class HttpServerConnection {
public:
explicit HttpServerConnection(std::shared_ptr<uv::Stream> stream);
virtual ~HttpServerConnection() = default;
protected:
/**
* Process an incoming HTTP request. This is called after the incoming
* message completes (e.g. from the HttpParser::messageComplete callback).
*
* The implementation should read request details from m_request and call the
* appropriate Send() functions to send a response back to the client.
*/
virtual void ProcessRequest() = 0;
/**
* Build common response headers.
*
* Called by SendHeader() to send headers common to every response.
* Each line must be terminated with \r\n.
*
* The default implementation sends the following:
* "Server: WebServer/1.0\r\n"
* "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
* "post-check=0, max-age=0\r\n"
* "Pragma: no-cache\r\n"
* "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n"
*
* These parameters should ensure the browser does not cache the response.
* A browser should connect for each file and not serve files from its cache.
*
* @param os response stream
*/
virtual void BuildCommonHeaders(raw_ostream& os);
/**
* Build HTTP response header, along with other header information like
* mimetype. Calls BuildCommonHeaders().
*
* @param os response stream
* @param code HTTP response code (e.g. 200)
* @param codeText HTTP response code text (e.g. "OK")
* @param contentType MIME content type (e.g. "text/plain")
* @param contentLength Length of content. If 0 is provided, m_keepAlive will
* be set to false.
* @param extra Extra HTTP headers to send, not including final "\r\n"
*/
void BuildHeader(raw_ostream& os, int code, const Twine& codeText,
const Twine& contentType, uint64_t contentLength,
const Twine& extra = Twine{});
/**
* Send data to client.
*
* This is a convenience wrapper around m_stream.Write() to provide
* auto-close functionality.
*
* @param bufs Buffers to write. Deallocate() will be called on each
* buffer after the write completes. If different behavior
* is desired, call m_stream.Write() directly instead.
* @param closeAfter close the connection after the write completes
*/
void SendData(ArrayRef<uv::Buffer> bufs, bool closeAfter = false);
/**
* Send HTTP response, along with other header information like mimetype.
* Calls BuildHeader().
*
* @param code HTTP response code (e.g. 200)
* @param codeText HTTP response code text (e.g. "OK")
* @param contentType MIME content type (e.g. "text/plain")
* @param content Response message content
* @param extraHeader Extra HTTP headers to send, not including final "\r\n"
*/
void SendResponse(int code, const Twine& codeText, const Twine& contentType,
StringRef content, const Twine& extraHeader = Twine{});
/**
* Send error header and message.
* This provides standard code responses for 400, 401, 403, 404, 500, and 503.
* Other codes will be reported as 501. For arbitrary code handling, use
* SendResponse() instead.
*
* @param code HTTP error code (e.g. 404)
* @param message Additional message text
*/
void SendError(int code, const Twine& message = Twine{});
/** The HTTP request. */
HttpParser m_request{HttpParser::kRequest};
/** Whether the connection should be kept alive. */
bool m_keepAlive = false;
/** The underlying stream for the connection. */
uv::Stream& m_stream;
};
} // namespace wpi
#endif // WPIUTIL_WPI_HTTPSERVERCONNECTION_H_