• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CrowCpp / Crow / 810

10 Oct 2025 12:31AM UTC coverage: 87.349% (+0.01%) from 87.336%
810

push

gh-actions

gittiver
websockets: Allow sending a response when rejecting a websocket request (Closes #1097)

54 of 100 new or added lines in 4 files covered. (54.0%)

175 existing lines in 6 files now uncovered.

4129 of 4727 relevant lines covered (87.35%)

136.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.16
/include/crow/http_response.h
1
#pragma once
2
#include <string>
3
#include <unordered_map>
4
#include <ios>
5
#include <fstream>
6
#include <sstream>
7
// S_ISREG is not defined for windows
8
// This defines it like suggested in https://stackoverflow.com/a/62371749
9
#if defined(_MSC_VER)
10
#define _CRT_INTERNAL_NONSTDC_NAMES 1
11
#endif
12
#include <sys/stat.h>
13
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
14
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
15
#endif
16

17
#include "crow/http_request.h"
18
#include "crow/ci_map.h"
19
#include "crow/socket_adaptors.h"
20
#include "crow/logging.h"
21
#include "crow/mime_types.h"
22
#include "crow/returnable.h"
23

24

25
namespace crow
26
{
27
    template<typename Adaptor, typename Handler, typename... Middlewares>
28
    class Connection;
29

30
    namespace websocket
31
    {
32
        template<typename Adaptor, typename Handler>
33
        class Connection;
34
    }
35

36
    class Router;
37

38
    /// HTTP response
39
    struct response
40
    {
41
        template<typename Adaptor, typename Handler, typename... Middlewares>
42
        friend class crow::Connection;
43

44
        template<typename Adaptor, typename Handler>
45
        friend class websocket::Connection;
46

47
        friend class Router;
48

49
        int code{200};    ///< The Status code for the response.
50
        std::string body; ///< The actual payload containing the response data.
51
        ci_map headers;   ///< HTTP headers.
52

53
#ifdef CROW_ENABLE_COMPRESSION
54
        bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
55
#endif
56
        bool skip_body = false;            ///< Whether this is a response to a HEAD request.
57
        bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
58

59
        /// Set the value of an existing header in the response.
60
        void set_header(std::string key, std::string value)
53✔
61
        {
62
            headers.erase(key);
53✔
63
            headers.emplace(std::move(key), std::move(value));
53✔
64
        }
53✔
65

66
        /// Add a new header to the response.
67
        void add_header(std::string key, std::string value)
33✔
68
        {
69
            headers.emplace(std::move(key), std::move(value));
33✔
70
        }
33✔
71

72
        const std::string& get_header_value(const std::string& key)
16✔
73
        {
74
            return crow::get_header_value(headers, key);
16✔
75
        }
76

77
        // naive validation of a mime-type string
78
        static bool validate_mime_type(const std::string& candidate) noexcept
8✔
79
        {
80
            // Here we simply check that the candidate type starts with
81
            // a valid parent type, and has at least one character afterwards.
82
            std::array<std::string, 10> valid_parent_types = {
83
              "application/", "audio/", "font/", "example/",
84
              "image/", "message/", "model/", "multipart/",
85
              "text/", "video/"};
16✔
86
            for (const std::string& parent : valid_parent_types)
68✔
87
            {
88
                // ensure the candidate is *longer* than the parent,
89
                // to avoid unnecessary string comparison and to
90
                // reject zero-length subtypes.
91
                if (candidate.size() <= parent.size())
63✔
92
                {
93
                    continue;
31✔
94
                }
95
                // strncmp is used rather than substr to avoid allocation,
96
                // but a string_view approach would be better if Crow
97
                // migrates to C++17.
98
                if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
32✔
99
                {
100
                    return true;
3✔
101
                }
102
            }
103
            return false;
5✔
104
        }
8✔
105

106
        // Find the mime type from the content type either by lookup,
107
        // or by the content type itself, if it is a valid a mime type.
108
        // Defaults to text/plain.
109
        static std::string get_mime_type(const std::string& contentType)
18✔
110
        {
111
            const auto mimeTypeIterator = mime_types.find(contentType);
18✔
112
            if (mimeTypeIterator != mime_types.end())
18✔
113
            {
114
                return mimeTypeIterator->second;
10✔
115
            }
116
            else if (validate_mime_type(contentType))
8✔
117
            {
118
                return contentType;
3✔
119
            }
120
            else
121
            {
122
                CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
5✔
123
                return "text/plain";
10✔
124
            }
125
        }
126

127

128
        // clang-format off
129
        response() {}
255✔
130
        explicit response(int code_) : code(code_) {}
214✔
131
        response(std::string body_) : body(std::move(body_)) {}
87✔
132
        response(int code_, std::string body_) : code(code_), body(std::move(body_)) {}
6✔
133
        // clang-format on
134
        response(returnable&& value)
1✔
135
        {
1✔
136
            body = value.dump();
1✔
137
            set_header("Content-Type", value.content_type);
3✔
138
        }
1✔
139
        response(returnable& value)
140
        {
141
            body = value.dump();
142
            set_header("Content-Type", value.content_type);
143
        }
144
        response(int code_, returnable& value):
145
          code(code_)
146
        {
147
            body = value.dump();
148
            set_header("Content-Type", value.content_type);
149
        }
150
        response(int code_, returnable&& value):
151
          code(code_), body(value.dump())
152
        {
153
            set_header("Content-Type", std::move(value.content_type));
154
        }
155

UNCOV
156
        response(response&& r)
×
UNCOV
157
        {
×
UNCOV
158
            *this = std::move(r);
×
159
        }
160

161
        response(std::string contentType, std::string body_):
10✔
162
          body(std::move(body_))
10✔
163
        {
164
            set_header("Content-Type", get_mime_type(contentType));
30✔
165
        }
10✔
166

167
        response(int code_, std::string contentType, std::string body_):
6✔
168
          code(code_), body(std::move(body_))
6✔
169
        {
170
            set_header("Content-Type", get_mime_type(contentType));
18✔
171
        }
6✔
172

173
        response& operator=(const response& r) = delete;
174

175
        response& operator=(response&& r) noexcept
305✔
176
        {
177
            body = std::move(r.body);
305✔
178
            code = r.code;
305✔
179
            headers = std::move(r.headers);
305✔
180
            completed_ = r.completed_;
305✔
181
            file_info = std::move(r.file_info);
305✔
182
            return *this;
305✔
183
        }
184

185
        /// Check if the response has completed (whether response.end() has been called)
186
        bool is_completed() const noexcept
36✔
187
        {
188
            return completed_;
36✔
189
        }
190

191
        void clear()
152✔
192
        {
193
            body.clear();
152✔
194
            code = 200;
152✔
195
            headers.clear();
152✔
196
            completed_ = false;
152✔
197
            file_info = static_file_info{};
304✔
198
        }
152✔
199

200
        /// Return a "Temporary Redirect" response.
201

202
        ///
203
        /// Location can either be a route or a full URL.
204
        void redirect(const std::string& location)
205
        {
206
            code = 307;
207
            set_header("Location", location);
208
        }
209

210
        /// Return a "Permanent Redirect" response.
211

212
        ///
213
        /// Location can either be a route or a full URL.
214
        void redirect_perm(const std::string& location)
215
        {
216
            code = 308;
217
            set_header("Location", location);
218
        }
219

220
        /// Return a "Found (Moved Temporarily)" response.
221

222
        ///
223
        /// Location can either be a route or a full URL.
224
        void moved(const std::string& location)
225
        {
226
            code = 302;
227
            set_header("Location", location);
228
        }
229

230
        /// Return a "Moved Permanently" response.
231

232
        ///
233
        /// Location can either be a route or a full URL.
234
        void moved_perm(const std::string& location)
235
        {
236
            code = 301;
237
            set_header("Location", location);
238
        }
239

240
        void write(const std::string& body_part)
241
        {
242
            body += body_part;
243
        }
244

245
        /// Set the response completion flag and call the handler (to send the response).
246
        void end()
230✔
247
        {
248
            if (!completed_)
230✔
249
            {
250
                completed_ = true;
228✔
251
                if (skip_body)
228✔
252
                {
253
                    set_header("Content-Length", std::to_string(body.size()));
6✔
254
                    body = "";
2✔
255
                    manual_length_header = true;
2✔
256
                }
257
                if (complete_request_handler_)
228✔
258
                {
259
                    complete_request_handler_();
70✔
260
                    manual_length_header = false;
70✔
261
                    skip_body = false;
70✔
262
                }
263
            }
264
        }
230✔
265

266
        /// Same as end() except it adds a body part right before ending.
267
        void end(const std::string& body_part)
268
        {
269
            body += body_part;
270
            end();
271
        }
272

273
        /// Check if the connection is still alive (usually by checking the socket status).
274
        bool is_alive()
275
        {
276
            return is_alive_helper_ && is_alive_helper_();
277
        }
278

279
        /// Check whether the response has a static file defined.
280
        bool is_static_type()
75✔
281
        {
282
            return file_info.path.size();
75✔
283
        }
284

285
        /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
286

287
        ///
288
        /// Either a static file or a string body can be returned as 1 response.
289
        struct static_file_info
290
        {
291
            std::string path = "";
292
            struct stat statbuf;
293
            int statResult;
294
        };
295

296
        /// Return a static file as the response body, the content_type may be specified explicitly.
297
        void set_static_file_info(std::string path, std::string content_type = "")
4✔
298
        {
299
            utility::sanitize_filename(path);
4✔
300
            set_static_file_info_unsafe(path, content_type);
4✔
301
        }
4✔
302

303
        /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead),
304
        /// the content_type may be specified explicitly.
305
        void set_static_file_info_unsafe(std::string path, std::string content_type = "")
4✔
306
        {
307
            file_info.path = path;
4✔
308
            file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
4✔
309
#ifdef CROW_ENABLE_COMPRESSION
310
            compressed = false;
4✔
311
#endif
312
            if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
4✔
313
            {
314
                code = 200;
3✔
315
                this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
9✔
316

317
                if (content_type.empty())
3✔
318
                {
319
                    std::size_t last_dot = path.find_last_of('.');
2✔
320
                    std::string extension = path.substr(last_dot + 1);
2✔
321

322
                    if (!extension.empty())
2✔
323
                    {
324
                        this->add_header("Content-Type", get_mime_type(extension));
6✔
325
                    }
326
                }
2✔
327
                else
328
                {
329
                    this->add_header("Content-Type", content_type);
3✔
330
                }
331
            }
3✔
332
            else
333
            {
334
                code = 404;
1✔
335
                file_info.path.clear();
1✔
336
            }
337
        }
4✔
338

339
    private:
340
        void write_header_into_buffer(std::vector<asio::const_buffer>& buffers, std::string& content_length_buffer, bool add_keep_alive, const std::string& server_name)
75✔
341
        {
342
            // TODO(EDev): HTTP version in status codes should be dynamic
343
            // Keep in sync with common.h/status
344
            static std::unordered_map<int, std::string> statusCodes = {
NEW
345
              {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
×
NEW
346
              {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
×
347

NEW
348
              {status::OK, "HTTP/1.1 200 OK\r\n"},
×
NEW
349
              {status::CREATED, "HTTP/1.1 201 Created\r\n"},
×
NEW
350
              {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
×
NEW
351
              {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
×
NEW
352
              {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
×
NEW
353
              {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
×
NEW
354
              {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
×
355

NEW
356
              {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
×
NEW
357
              {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
×
NEW
358
              {status::FOUND, "HTTP/1.1 302 Found\r\n"},
×
NEW
359
              {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
×
NEW
360
              {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
×
NEW
361
              {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
×
NEW
362
              {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
×
363

NEW
364
              {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
×
NEW
365
              {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
×
NEW
366
              {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
×
NEW
367
              {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
×
NEW
368
              {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
×
NEW
369
              {status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"},
×
NEW
370
              {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
×
NEW
371
              {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
×
NEW
372
              {status::GONE, "HTTP/1.1 410 Gone\r\n"},
×
NEW
373
              {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
×
NEW
374
              {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
×
NEW
375
              {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
×
NEW
376
              {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
×
NEW
377
              {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
×
NEW
378
              {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
×
NEW
379
              {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
×
380

NEW
381
              {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
×
NEW
382
              {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
×
NEW
383
              {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
×
NEW
384
              {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
×
NEW
385
              {status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"},
×
NEW
386
              {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
×
387
            };
155✔
388

389
            static const std::string seperator = ": ";
79✔
390

391
            buffers.clear();
75✔
392
            buffers.reserve(4 * (headers.size() + 5) + 3);
75✔
393

394
            if (!statusCodes.count(code))
75✔
395
            {
396
                CROW_LOG_WARNING << this << " status code "
2✔
397
                                 << "(" << code << ")"
1✔
398
                                 << " not defined, returning 500 instead";
1✔
399
                code = 500;
1✔
400
            }
401

402
            auto& status = statusCodes.find(code)->second;
75✔
403
            buffers.emplace_back(status.data(), status.size());
75✔
404

405
            if (code >= 400 && body.empty())
75✔
406
                body = statusCodes[code].substr(9);
2✔
407

408
            for (auto& kv : headers)
106✔
409
            {
410
                buffers.emplace_back(kv.first.data(), kv.first.size());
31✔
411
                buffers.emplace_back(seperator.data(), seperator.size());
31✔
412
                buffers.emplace_back(kv.second.data(), kv.second.size());
31✔
413
                buffers.emplace_back(crlf.data(), crlf.size());
31✔
414
            }
415

416
            if (!manual_length_header && !headers.count("content-length"))
225✔
417
            {
418
                content_length_buffer = std::to_string(body.size());
75✔
419
                static std::string content_length_tag = "Content-Length: ";
79✔
420
                buffers.emplace_back(content_length_tag.data(), content_length_tag.size());
75✔
421
                buffers.emplace_back(content_length_buffer.data(), content_length_buffer.size());
75✔
422
                buffers.emplace_back(crlf.data(), crlf.size());
75✔
423
            }
424
            if (!headers.count("server") && !server_name.empty())
225✔
425
            {
426
                static std::string server_tag = "Server: ";
79✔
427
                buffers.emplace_back(server_tag.data(), server_tag.size());
75✔
428
                buffers.emplace_back(server_name.data(), server_name.size());
75✔
429
                buffers.emplace_back(crlf.data(), crlf.size());
75✔
430
            }
431
            /*if (!headers.count("date"))
432
            {
433
                static std::string date_tag = "Date: ";
434
                date_str_ = get_cached_date_str();
435
                buffers.emplace_back(date_tag.data(), date_tag.size());
436
                buffers.emplace_back(date_str_.data(), date_str_.size());
437
                buffers.emplace_back(crlf.data(), crlf.size());
438
            }*/
439
            if (add_keep_alive)
75✔
440
            {
441
                static std::string keep_alive_tag = "Connection: Keep-Alive";
29✔
442
                buffers.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
27✔
443
                buffers.emplace_back(crlf.data(), crlf.size());
27✔
444
            }
445

446
            buffers.emplace_back(crlf.data(), crlf.size());
75✔
447
        }
77✔
448

449
        bool completed_{};
450
        std::function<void()> complete_request_handler_;
451
        std::function<bool()> is_alive_helper_;
452
        static_file_info file_info;
453
    };
454
} // namespace crow
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc