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