-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
internal_http_service.h
210 lines (191 loc) · 9.91 KB
/
internal_http_service.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#pragma once
#include <map>
#include <optional>
#include <string>
#include <utility>
#include "drake/common/drake_copyable.h"
namespace drake {
namespace geometry {
namespace render_gltf_client {
namespace internal {
/* Three type aliases to interact with HttpService. */
using DataFieldsMap = std::map<std::string, std::string>;
using FileFieldPayload = std::pair<std::string, std::optional<std::string>>;
using FileFieldsMap =
std::map<std::string, std::pair<std::string, std::optional<std::string>>>;
/* A simple wrapper struct to encapsulate an HTTP server response. */
struct HttpResponse {
/* The HTTP response code from the server. Note that in the special case of
an HttpService not being able to connect to a server (e.g., the wrong url
was provided or the server is not running), this field will often have a
value of `0`. Use HttpResponse::Good() to validate server responses. */
int http_code{0};
/* In the event that the server has provided a text or binary response in
addition to its HTTP response code, the file path described by this attribute
will contain the response. An HttpService will populate this field with a
non-null value if and only if a response from the server was provided.
@note
This is **not** a response loaded into a string, it is a path to a
non-empty file that contains the response. The **caller** is responsible
for loading this file as the appropriate file type, **and** for deleting
the file when it is no longer needed. */
std::optional<std::string> data_path{std::nullopt};
/* In the event that there was an error with the HttpService processing the
request, a non-nullopt service_error_message will be provided.
@note
Any forms of std::string, including the empty string, will be treated as
a service error when checking via HttpResponse::Good().
*/
std::optional<std::string> service_error_message{std::nullopt};
/* Whether or not the HTTP transaction was successful. This includes
verifying that the HttpResponse::http_code indicates success, but also
whether or not the underlying HttpService marked it for failure via
a null check in HttpResponse::service_error_message. Note that if a server
has not been properly implemented, it may always return `200` even if the
response text indicates failure. This is an error with the server, and there
is nothing this framework can do to validate such a scenario.
@note
A redirection response code (300-399) is currently considered a successful
transaction. When and how a server should return a 3xx code is out of the
scope of Drake. */
bool Good() const {
return !service_error_message.has_value() &&
(http_code >= 200 && http_code < 400);
}
};
/* An HTTP service API, used by a RenderClient, to facilitate server
communications. This class is not intended to be used on its own. It is a
helper class to perform server communications, but does not have any specific
knowledge of a given client-server API. The owning entity, such as
RenderClient, is responsible for adhering to the server API. */
class HttpService {
public:
HttpService() {}
virtual ~HttpService() {}
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(HttpService);
/* @name Server Interaction Interface */
//@{
/* Posts an HTML `<form>` to the specified `url`.
An HTML `<form>` may allow for a wide range of different kinds of `<input>`
data. For the full listing of available input `type`'s, see the
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">
<tt><input></tt> documentation</a>. For many of these types, the way
an %HttpService will actually POST the data to the server will be the same, a
`name` and `value` are provided and the server takes care of validating the
`type`. For example, an `<input type="number" name="age" min="0" max="150">`
on the server will be posted to with simply `age={value}`. If the `{value}`
is invalid, the server will respond indicating so. Similarly, an
`<input type="checkbox" name="check">` would have a submission of just
`check=on` or `check=off`. Implementations are allowed to make the following
assumptions:
1. The server `<form>` is _valid_, namely that every `<input>` has a `name`
attribute.
2. Every input `value` being submitted corresponds appropriately with the
provided field `name`. That is, no validation of the provided `<input>`s
is performed -- the `<form>` is simply submitted. In the event that the
caller has provided invalid data for a `name={value}` pair, the server
is expected to respond with an error code and message indicating this. If
it does, this will be provided in the returned HttpResponse.
@note
Callers are encouraged to provide the `<input type="submit">` entry in the
provided `data_fields`. Although providing this entry is often not
required, it is encouraged.
Since file uploads (`<input type="file">`) are a special case, often
requiring the underlying implementation to wield different machinery, these
are required as a separate parameter. This also enables the implementation
to validate that the file path(s) provided exist.
Finally, the %HttpService has no way of knowing what kind of a response to
expect from the server. A server may only provide an HTTP response code,
a response code and an error message, or a response code and a binary file
response. As such, the %HttpService will expect to receive a response in
addition to an HTTP return code. In the event that the server responds with
data of any kind (text or binary), the returned HttpResponse::data_path will
have the path to the file where this information was saved.
Some examples for filling out simple `<form>`s:
@code{.cpp}
// A simple form that does not upload any files nor return anything.
// <form>
// <input type="text" name="name">
// <input type="number" name="age">
// <!-- NOTE: often, the `value` is irrelevant. -->
// <input type="submit" name="submit" value="Submit">
// <form>
// Submit form to /hello:port/endpoint:
auto response_1 = http_service_->PostForm(
temp_dir, "/hello:port/endpoint",
{{"name", "Sue"}, {"age", "34"}, {"submit", "Submit"}},
{}); // No file fields.
// Suppose `<input type="file" name="photo">` and
// `<input type="file" name="id">` were added.
auto response_2 = http_service->PostForm(
temp_dir, "/hello:port/endpoint",
{{"name", "Bo"}, {"age", "49"}, {"submit", "Submit"}},
{{"photo", {"/path/to/bo-profile.jpg", "image/jpeg"}},
// No mime-type for this file, std::nullopt indicates this.
{"id", {"/path/to/bo-id.bin", std::nullopt}}});
@endcode
Make sure to check HttpResponse::Good() before continuing, or trying to load
the HttpResponse::data_path as any specific kind of file.
@param temp_directory
The (shared) temporary directory, e.g., as created from
drake::temp_directory(). File responses from a server will be stored here.
The %HttpService does **not** own this directory, and is not responsible
for deleting it. No validity checks on this directory are performed.
@param url
The url this HTTP service will communicate with.
@param data_fields
The entries for the `<input>` elements of the form. Keys are the field
name, and values are the field values. For example:
@code{.cpp}
std::map<std::string, std::string> data_fields;
// <input type="text" name="scene_sha256">
data_fields["scene_sha256"] = "... hash value ...";
// <input type="number" name="width">
data_fields["width"] = "640";
@endcode
Do **not** zero pad numeric values, a given server may have trouble parsing
unanticipated numeric values. Supply the empty map if no data fields
should be posted.
@param file_fields
Any entries for `<input type="file">` elements of the form. Keys are the
field name, and values are the (file path, optional mimetype) pair. For
example:
@code{.cpp}
FileFieldsMap file_fields;
// <input type="file" name="scene">, known mimetype
file_fields["scene"] = {"/path/to/scene_file.gltf", "model/gltf+json"};
// <input type="file" name="id">, unknown mimetype
file_fields["id"] = {"/path/to/id.bin", std::nullopt};
@endcode
Supply the empty map if no files are to be uploaded with the `<form>`.
@param verbose
Whether or not client/server communications should be logged to the
console.
@return HttpResponse
The response from the server, including an HTTP code, and any potential
additional server response text or data in HttpResponse::data_path.
@throws std::exception
An exception may also be thrown if an irrecoverable error is encountered,
e.g., a file path provided in one of the values of `file_fields` does not
exist. Failed communications such as an inability to connect, or an
invalid HTTP response code, should **not** produce an exception but rather
encode this information in the returned HttpResponse for the caller to
determine how to proceed. */
HttpResponse PostForm(const std::string& temp_directory,
const std::string& url,
const DataFieldsMap& data_fields,
const FileFieldsMap& file_fields, bool verbose = false);
protected:
/* The NVI-function for posting an HTML form to a render server. When
PostForm calls this, it has already validated the existence of the files in
`file_fields`. */
virtual HttpResponse DoPostForm(const std::string& temp_directory,
const std::string& url,
const DataFieldsMap& data_fields,
const FileFieldsMap& file_fields,
bool verbose) = 0;
};
} // namespace internal
} // namespace render_gltf_client
} // namespace geometry
} // namespace drake