xref: /haiku/src/kits/network/libnetservices2/HttpSerializer.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
1 /*
2  * Copyright 2022 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Niels Sascha Reedijk, niels.reedijk@gmail.com
7  */
8 
9 #include "HttpSerializer.h"
10 
11 #include <DataIO.h>
12 #include <HttpRequest.h>
13 #include <NetServicesDefs.h>
14 
15 #include "HttpBuffer.h"
16 
17 using namespace std::literals;
18 using namespace BPrivate::Network;
19 
20 
21 /*!
22 	\brief Set the \a request to serialize, and load the initial data into the \a buffer.
23 */
24 void
25 HttpSerializer::SetTo(HttpBuffer& buffer, const BHttpRequest& request)
26 {
27 	buffer.Clear();
28 	request.SerializeHeaderTo(buffer);
29 	fState = HttpSerializerState::Header;
30 
31 	if (auto requestBody = request.RequestBody()) {
32 		fBody = requestBody->input.get();
33 		if (requestBody->size) {
34 			fBodySize = *(requestBody->size);
35 		}
36 	}
37 }
38 
39 
40 /*!
41 	\brief Transfer the HTTP request to \a target while using \a buffer for intermediate storage.
42 
43 	\returns The number of body bytes written during the call.
44 */
45 size_t
46 HttpSerializer::Serialize(HttpBuffer& buffer, BDataIO* target)
47 {
48 	bool finishing = false;
49 	size_t bodyBytesWritten = 0;
50 	while (!finishing) {
51 		switch (fState) {
52 			case HttpSerializerState::Uninitialized:
53 				throw BRuntimeError(__PRETTY_FUNCTION__, "Invalid state: Uninitialized");
54 
55 			case HttpSerializerState::Header:
56 				_WriteToTarget(buffer, target);
57 				if (buffer.RemainingBytes() > 0) {
58 					// There are more bytes to be processed; wait for the next iteration
59 					return 0;
60 				}
61 
62 				if (fBody == nullptr) {
63 					fState = HttpSerializerState::Done;
64 					return 0;
65 				} else if (_IsChunked())
66 					// fState = HttpSerializerState::ChunkHeader;
67 					throw BRuntimeError(
68 						__PRETTY_FUNCTION__, "Chunked serialization not implemented");
69 				else
70 					fState = HttpSerializerState::Body;
71 				break;
72 
73 			case HttpSerializerState::Body:
74 			{
75 				auto bytesWritten = _WriteToTarget(buffer, target);
76 				bodyBytesWritten += bytesWritten;
77 				fTransferredBodySize += bytesWritten;
78 				if (buffer.RemainingBytes() > 0) {
79 					// did not manage to write all the bytes in the buffer; continue in the next
80 					// round
81 					finishing = true;
82 					break;
83 				}
84 
85 				if (fBodySize && fBodySize.value() == fTransferredBodySize) {
86 					fState = HttpSerializerState::Done;
87 					finishing = true;
88 				}
89 				break;
90 			}
91 
92 			case HttpSerializerState::Done:
93 			default:
94 				finishing = true;
95 				continue;
96 		}
97 
98 		// Load more data into the buffer
99 		std::optional<size_t> maxReadSize = std::nullopt;
100 		if (fBodySize)
101 			maxReadSize = fBodySize.value() - fTransferredBodySize;
102 		buffer.ReadFrom(fBody, maxReadSize);
103 	}
104 
105 	return bodyBytesWritten;
106 }
107 
108 
109 bool
110 HttpSerializer::_IsChunked() const noexcept
111 {
112 	return fBodySize == std::nullopt;
113 }
114 
115 
116 size_t
117 HttpSerializer::_WriteToTarget(HttpBuffer& buffer, BDataIO* target) const
118 {
119 	size_t bytesWritten = 0;
120 	buffer.WriteTo([target, &bytesWritten](const std::byte* buffer, size_t size) {
121 		ssize_t result = B_INTERRUPTED;
122 		while (result == B_INTERRUPTED) {
123 			result = target->Write(buffer, size);
124 		}
125 
126 		if (result <= 0 && result != B_WOULD_BLOCK) {
127 			throw BNetworkRequestError(
128 				__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError, result);
129 		} else if (result > 0) {
130 			bytesWritten += result;
131 			return size_t(result);
132 		} else {
133 			return size_t(0);
134 		}
135 	});
136 
137 	return bytesWritten;
138 }
139