blob: 313ee2b06789079a21c238b8a82b5e66ca93935d [file] [log] [blame]
James Kuszmaulcf324122023-01-14 14:07:17 -08001// 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.
4
5#include "wpinet/HttpServerConnection.h"
6
7#include <fmt/format.h>
8#include <wpi/SmallString.h>
9#include <wpi/SmallVector.h>
10#include <wpi/SpanExtras.h>
11#include <wpi/StringExtras.h>
12#include <wpi/fmt/raw_ostream.h>
13
14#include "wpinet/raw_uv_ostream.h"
15
16using namespace wpi;
17
18HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
19 : m_stream(*stream) {
20 // process HTTP messages
21 m_messageCompleteConn =
22 m_request.messageComplete.connect_connection([this](bool keepAlive) {
23 m_keepAlive = keepAlive;
24 ProcessRequest();
25 });
26
27 // look for Accept-Encoding headers to determine if gzip is acceptable
28 m_request.messageBegin.connect([this] { m_acceptGzip = false; });
29 m_request.header.connect(
30 [this](std::string_view name, std::string_view value) {
31 if (wpi::equals_lower(name, "accept-encoding") &&
32 wpi::contains(value, "gzip")) {
33 m_acceptGzip = true;
34 }
35 });
36
37 // pass incoming data to HTTP parser
38 m_dataConn =
39 stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
40 m_request.Execute({buf.base, size});
41 if (m_request.HasError()) {
42 // could not parse; just close the connection
43 m_stream.Close();
44 }
45 });
46
47 // close when remote side closes
48 m_endConn =
49 stream->end.connect_connection([h = stream.get()] { h->Close(); });
50
51 // start reading
52 stream->StartRead();
53}
54
55void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
56 os << "Server: WebServer/1.0\r\n"
57 "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
58 "post-check=0, max-age=0\r\n"
59 "Pragma: no-cache\r\n"
60 "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
61}
62
63void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
64 std::string_view codeText,
65 std::string_view contentType,
66 uint64_t contentLength,
67 std::string_view extra) {
68 fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
69 m_request.GetMinor(), code, codeText);
70 if (contentLength == 0) {
71 m_keepAlive = false;
72 }
73 if (!m_keepAlive) {
74 os << "Connection: close\r\n";
75 }
76 BuildCommonHeaders(os);
77 os << "Content-Type: " << contentType << "\r\n";
78 if (contentLength != 0) {
79 fmt::print(os, "Content-Length: {}\r\n", contentLength);
80 }
81 os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
82 if (!extra.empty()) {
83 os << extra;
84 }
85 os << "\r\n"; // header ends with a blank line
86}
87
88void HttpServerConnection::SendData(std::span<const uv::Buffer> bufs,
89 bool closeAfter) {
90 m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
91 for (auto&& buf : bufs) {
92 buf.Deallocate();
93 }
94 if (closeAfter) {
95 stream->Close();
96 }
97 });
98}
99
100void HttpServerConnection::SendResponse(int code, std::string_view codeText,
101 std::string_view contentType,
102 std::string_view content,
103 std::string_view extraHeader) {
104 SmallVector<uv::Buffer, 4> toSend;
105 raw_uv_ostream os{toSend, 4096};
106 BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
107 os << content;
108 // close after write completes if we aren't keeping alive
109 SendData(os.bufs(), !m_keepAlive);
110}
111
112void HttpServerConnection::SendStaticResponse(
113 int code, std::string_view codeText, std::string_view contentType,
114 std::string_view content, bool gzipped, std::string_view extraHeader) {
115 // TODO: handle remote side not accepting gzip (very rare)
116
117 std::string_view contentEncodingHeader;
118 if (gzipped /* && m_acceptGzip*/) {
119 contentEncodingHeader = "Content-Encoding: gzip\r\n";
120 }
121
122 SmallVector<uv::Buffer, 4> bufs;
123 raw_uv_ostream os{bufs, 4096};
124 BuildHeader(os, code, codeText, contentType, content.size(),
125 fmt::format("{}{}", extraHeader, contentEncodingHeader));
126 // can send content without copying
127 bufs.emplace_back(content);
128
129 m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
130 auto bufs, uv::Error) {
131 // don't deallocate the static content
132 for (auto&& buf : wpi::drop_back(bufs)) {
133 buf.Deallocate();
134 }
135 if (closeAfter) {
136 stream->Close();
137 }
138 });
139}
140
141void HttpServerConnection::SendError(int code, std::string_view message) {
142 std::string_view codeText, extra, baseMessage;
143 switch (code) {
144 case 401:
145 codeText = "Unauthorized";
146 extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
147 baseMessage = "401: Not Authenticated!";
148 break;
149 case 404:
150 codeText = "Not Found";
151 baseMessage = "404: Not Found!";
152 break;
153 case 500:
154 codeText = "Internal Server Error";
155 baseMessage = "500: Internal Server Error!";
156 break;
157 case 400:
158 codeText = "Bad Request";
159 baseMessage = "400: Not Found!";
160 break;
161 case 403:
162 codeText = "Forbidden";
163 baseMessage = "403: Forbidden!";
164 break;
165 case 503:
166 codeText = "Service Unavailable";
167 baseMessage = "503: Service Unavailable";
168 break;
169 default:
170 code = 501;
171 codeText = "Not Implemented";
172 baseMessage = "501: Not Implemented!";
173 break;
174 }
175 SmallString<256> content{baseMessage};
176 content += "\r\n";
177 content += message;
178 SendResponse(code, codeText, "text/plain", content.str(), extra);
179}