blob: 716d2af11a913e41916ec79913141e27064a6cf8 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
Brian Silverman8fce7482020-01-05 13:18:21 -08004
5#include "wpi/HttpServerConnection.h"
6
Austin Schuh812d0d12021-11-04 20:16:48 -07007#include "fmt/format.h"
Brian Silverman8fce7482020-01-05 13:18:21 -08008#include "wpi/SmallString.h"
9#include "wpi/SmallVector.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070010#include "wpi/SpanExtras.h"
11#include "wpi/StringExtras.h"
12#include "wpi/fmt/raw_ostream.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080013#include "wpi/raw_uv_ostream.h"
14
15using namespace wpi;
16
17HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
18 : m_stream(*stream) {
19 // process HTTP messages
20 m_messageCompleteConn =
21 m_request.messageComplete.connect_connection([this](bool keepAlive) {
22 m_keepAlive = keepAlive;
23 ProcessRequest();
24 });
25
26 // look for Accept-Encoding headers to determine if gzip is acceptable
27 m_request.messageBegin.connect([this] { m_acceptGzip = false; });
Austin Schuh812d0d12021-11-04 20:16:48 -070028 m_request.header.connect(
29 [this](std::string_view name, std::string_view value) {
30 if (wpi::equals_lower(name, "accept-encoding") &&
31 wpi::contains(value, "gzip")) {
32 m_acceptGzip = true;
33 }
34 });
Brian Silverman8fce7482020-01-05 13:18:21 -080035
36 // pass incoming data to HTTP parser
37 m_dataConn =
38 stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
Austin Schuh812d0d12021-11-04 20:16:48 -070039 m_request.Execute({buf.base, size});
Brian Silverman8fce7482020-01-05 13:18:21 -080040 if (m_request.HasError()) {
41 // could not parse; just close the connection
42 m_stream.Close();
43 }
44 });
45
46 // close when remote side closes
47 m_endConn =
48 stream->end.connect_connection([h = stream.get()] { h->Close(); });
49
50 // start reading
51 stream->StartRead();
52}
53
54void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
55 os << "Server: WebServer/1.0\r\n"
56 "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
57 "post-check=0, max-age=0\r\n"
58 "Pragma: no-cache\r\n"
59 "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
60}
61
62void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
Austin Schuh812d0d12021-11-04 20:16:48 -070063 std::string_view codeText,
64 std::string_view contentType,
Brian Silverman8fce7482020-01-05 13:18:21 -080065 uint64_t contentLength,
Austin Schuh812d0d12021-11-04 20:16:48 -070066 std::string_view extra) {
67 fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
68 m_request.GetMinor(), code, codeText);
69 if (contentLength == 0) {
70 m_keepAlive = false;
71 }
72 if (!m_keepAlive) {
73 os << "Connection: close\r\n";
74 }
Brian Silverman8fce7482020-01-05 13:18:21 -080075 BuildCommonHeaders(os);
76 os << "Content-Type: " << contentType << "\r\n";
Austin Schuh812d0d12021-11-04 20:16:48 -070077 if (contentLength != 0) {
78 fmt::print(os, "Content-Length: {}\r\n", contentLength);
79 }
Brian Silverman8fce7482020-01-05 13:18:21 -080080 os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
Austin Schuh812d0d12021-11-04 20:16:48 -070081 if (!extra.empty()) {
82 os << extra;
83 }
Brian Silverman8fce7482020-01-05 13:18:21 -080084 os << "\r\n"; // header ends with a blank line
85}
86
Austin Schuh812d0d12021-11-04 20:16:48 -070087void HttpServerConnection::SendData(span<const uv::Buffer> bufs,
Brian Silverman8fce7482020-01-05 13:18:21 -080088 bool closeAfter) {
Austin Schuh812d0d12021-11-04 20:16:48 -070089 m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
90 for (auto&& buf : bufs) {
91 buf.Deallocate();
92 }
93 if (closeAfter) {
94 stream->Close();
95 }
Brian Silverman8fce7482020-01-05 13:18:21 -080096 });
97}
98
Austin Schuh812d0d12021-11-04 20:16:48 -070099void HttpServerConnection::SendResponse(int code, std::string_view codeText,
100 std::string_view contentType,
101 std::string_view content,
102 std::string_view extraHeader) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800103 SmallVector<uv::Buffer, 4> toSend;
104 raw_uv_ostream os{toSend, 4096};
105 BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
106 os << content;
107 // close after write completes if we aren't keeping alive
108 SendData(os.bufs(), !m_keepAlive);
109}
110
Austin Schuh812d0d12021-11-04 20:16:48 -0700111void HttpServerConnection::SendStaticResponse(
112 int code, std::string_view codeText, std::string_view contentType,
113 std::string_view content, bool gzipped, std::string_view extraHeader) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800114 // TODO: handle remote side not accepting gzip (very rare)
115
Austin Schuh812d0d12021-11-04 20:16:48 -0700116 std::string_view contentEncodingHeader;
117 if (gzipped /* && m_acceptGzip*/) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800118 contentEncodingHeader = "Content-Encoding: gzip\r\n";
Austin Schuh812d0d12021-11-04 20:16:48 -0700119 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800120
121 SmallVector<uv::Buffer, 4> bufs;
122 raw_uv_ostream os{bufs, 4096};
123 BuildHeader(os, code, codeText, contentType, content.size(),
Austin Schuh812d0d12021-11-04 20:16:48 -0700124 fmt::format("{}{}", extraHeader, contentEncodingHeader));
Brian Silverman8fce7482020-01-05 13:18:21 -0800125 // can send content without copying
126 bufs.emplace_back(content);
127
128 m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
Austin Schuh812d0d12021-11-04 20:16:48 -0700129 auto bufs, uv::Error) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800130 // don't deallocate the static content
Austin Schuh812d0d12021-11-04 20:16:48 -0700131 for (auto&& buf : wpi::drop_back(bufs)) {
132 buf.Deallocate();
133 }
134 if (closeAfter) {
135 stream->Close();
136 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800137 });
138}
139
Austin Schuh812d0d12021-11-04 20:16:48 -0700140void HttpServerConnection::SendError(int code, std::string_view message) {
141 std::string_view codeText, extra, baseMessage;
Brian Silverman8fce7482020-01-05 13:18:21 -0800142 switch (code) {
143 case 401:
144 codeText = "Unauthorized";
145 extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
146 baseMessage = "401: Not Authenticated!";
147 break;
148 case 404:
149 codeText = "Not Found";
150 baseMessage = "404: Not Found!";
151 break;
152 case 500:
153 codeText = "Internal Server Error";
154 baseMessage = "500: Internal Server Error!";
155 break;
156 case 400:
157 codeText = "Bad Request";
158 baseMessage = "400: Not Found!";
159 break;
160 case 403:
161 codeText = "Forbidden";
162 baseMessage = "403: Forbidden!";
163 break;
164 case 503:
165 codeText = "Service Unavailable";
166 baseMessage = "503: Service Unavailable";
167 break;
168 default:
169 code = 501;
170 codeText = "Not Implemented";
171 baseMessage = "501: Not Implemented!";
172 break;
173 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700174 SmallString<256> content{baseMessage};
Brian Silverman8fce7482020-01-05 13:18:21 -0800175 content += "\r\n";
Austin Schuh812d0d12021-11-04 20:16:48 -0700176 content += message;
177 SendResponse(code, codeText, "text/plain", content.str(), extra);
Brian Silverman8fce7482020-01-05 13:18:21 -0800178}