42 constexpr
size_t kProgressInterval = 15;
44 constexpr
const char* kCookieFileName =
"net_cookies.dat";
46 size_t UploadCallback(
void* buffer,
size_t member_size,
size_t member_count,
48 auto* request =
reinterpret_cast<XMLHttpRequest*
>(user_data);
49 auto* buffer_bytes =
reinterpret_cast<uint8_t*
>(buffer);
50 size_t total_size = member_size * member_count;
51 return request->OnUpload(buffer_bytes, total_size);
54 size_t DownloadCallback(
void* buffer,
size_t member_size,
size_t member_count,
56 auto* request =
reinterpret_cast<XMLHttpRequest*
>(user_data);
57 auto* buffer_bytes =
reinterpret_cast<uint8_t*
>(buffer);
58 size_t total_size = member_size * member_count;
59 request->OnDataReceived(buffer_bytes, total_size);
63 size_t HeaderCallback(
char* buffer,
size_t member_size,
size_t member_count,
65 auto* request =
reinterpret_cast<XMLHttpRequest*
>(user_data);
66 auto* buffer_bytes =
reinterpret_cast<uint8_t*
>(buffer);
67 size_t total_size = member_size * member_count;
68 request->OnHeaderReceived(buffer_bytes, total_size);
72 double CurrentDownloadSize(CURL* curl) {
74 CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &ret));
90 bool ParseStatusLine(
const char* buffer,
size_t length,
int* code,
91 const char**
message,
size_t* message_length) {
94 constexpr
const char kMinStatusLine[] =
"HTTP/1.1 200 \r\n";
95 constexpr
const char kHttp10[] =
"HTTP/1.0 ";
96 constexpr
const char kHttp11[] =
"HTTP/1.1 ";
97 constexpr
const size_t kMinStatusSize =
sizeof(kMinStatusLine) - 1;
98 constexpr
const size_t kHttp10Size =
sizeof(kHttp10) - 1;
99 constexpr
const size_t kHttp11Size =
sizeof(kHttp11) - 1;
100 constexpr
const size_t kStatusSize = 3;
102 if (length < kMinStatusSize)
104 if (strncmp(buffer, kHttp10, kHttp10Size) != 0 &&
105 strncmp(buffer, kHttp11, kHttp11Size) != 0)
109 const char* status_code = buffer + kHttp10Size;
110 const char* status_code_end = status_code + kStatusSize;
111 if (!std::isdigit(status_code[0]) || !std::isdigit(status_code[1]) ||
112 !std::isdigit(status_code[2])) {
115 if (status_code_end[0] !=
' ')
117 if (buffer[length - 2] !=
'\r' || buffer[length - 1] !=
'\n')
120 *code = std::stoi(std::string(status_code, status_code_end));
121 *message = status_code_end + 1;
122 *message_length = length - kMinStatusSize;
130 mutex_(
"XMLHttpRequest"),
131 curl_(curl_easy_init()),
132 request_headers_(nullptr),
133 with_credentials_(false) {
147 XMLHttpRequest::~XMLHttpRequest() {
149 abort_pending_ =
true;
152 curl_easy_cleanup(curl_);
153 if (request_headers_)
154 curl_slist_free_all(request_headers_);
155 request_headers_ =
nullptr;
161 EventTarget::Trace(tracer);
162 std::unique_lock<Mutex> lock(mutex_);
164 tracer->
Trace(&upload_data_);
175 abort_pending_ =
true;
178 std::unique_lock<Mutex> lock(mutex_);
182 RaiseEvent<events::Event>(EventType::ReadyStateChange);
184 double total_size = CurrentDownloadSize(curl_);
185 RaiseEvent<events::ProgressEvent>(EventType::Progress,
true, total_size,
187 RaiseEvent<events::Event>(EventType::Abort);
188 RaiseEvent<events::ProgressEvent>(EventType::LoadEnd,
true, total_size,
198 std::unique_lock<Mutex> lock(mutex_);
200 for (
auto it : response_headers_) {
201 ret.append(it.first +
": " + it.second +
"\r\n");
207 const std::string&
name)
const {
208 std::unique_lock<Mutex> lock(mutex_);
209 auto it = response_headers_.find(name);
210 if (it == response_headers_.end())
216 const std::string& url,
222 "Synchronous requests are not supported.");
229 std::unique_lock<Mutex> lock(mutex_);
231 ScheduleEvent<events::Event>(EventType::ReadyStateChange);
233 curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
234 curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, method.c_str());
235 if (method ==
"HEAD")
236 curl_easy_setopt(curl_, CURLOPT_NOBODY, 1L);
239 curl_easy_setopt(curl_, CURLOPT_USERNAME, user->c_str());
241 curl_easy_setopt(curl_, CURLOPT_PASSWORD, password->c_str());
249 const bool contains_request =
253 std::unique_lock<Mutex> lock(mutex_);
257 "The object's state must be OPENED.");
265 if (maybe_data.has_value()) {
266 if (holds_alternative<ByteBuffer>(*maybe_data)) {
267 upload_data_ = std::move(get<ByteBuffer>(*maybe_data));
269 ByteString* str = &get<ByteString>(*maybe_data);
274 curl_easy_setopt(curl_, CURLOPT_UPLOAD, 1L);
275 curl_easy_setopt(curl_, CURLOPT_INFILESIZE_LARGE,
276 static_cast<curl_off_t>(upload_data_.
size()));
277 curl_easy_setopt(curl_, CURLOPT_READDATA,
this);
278 curl_easy_setopt(curl_, CURLOPT_READFUNCTION, UploadCallback);
280 curl_easy_setopt(curl_, CURLOPT_UPLOAD, 0L);
283 curl_easy_setopt(curl_, CURLOPT_TIMEOUT_MS,
285 curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, request_headers_);
294 const std::string& value) {
295 std::unique_lock<Mutex> lock(mutex_);
298 "The object's state must be OPENED.");
300 const std::string header = key +
": " + value;
301 request_headers_ = curl_slist_append(request_headers_, header.c_str());
306 return with_credentials_;
314 "withCredentials can only be set if the object's state is UNSENT or " 317 with_credentials_ = with_credentials;
321 void XMLHttpRequest::RaiseProgressEvents() {
327 RaiseEvent<events::Event>(EventType::ReadyStateChange);
331 RaiseEvent<events::Event>(EventType::ReadyStateChange);
336 std::unique_lock<Mutex> lock(mutex_);
337 cur_size = CurrentDownloadSize(curl_);
339 RaiseEvent<events::ProgressEvent>(EventType::Progress, estimated_size_ != 0,
340 cur_size, estimated_size_);
344 std::unique_lock<Mutex> lock(mutex_);
349 if (!abort_pending_ && now - last_progress_time_ >= kProgressInterval) {
350 last_progress_time_ = now;
355 std::bind(&XMLHttpRequest::RaiseProgressEvents, req));
362 std::unique_lock<Mutex> lock(mutex_);
370 auto* str =
reinterpret_cast<const char*
>(buffer);
371 if (!parsing_headers_) {
374 if (!ParseStatusLine(str, length, &
status, &message, &message_size))
376 status_text.assign(message, message + message_size);
378 parsing_headers_ =
true;
381 response_headers_.clear();
384 const char* sep = std::find(str, str + length,
':');
386 const size_t key_len = sep - str;
387 const size_t rest_len = length - key_len;
388 if (rest_len >= 2 && sep[rest_len - 2] ==
'\r' &&
389 sep[rest_len - 1] ==
'\n') {
394 if (response_headers_.count(key) == 0)
395 response_headers_[key] = value;
397 response_headers_[key] +=
", " + value;
400 if (key ==
"content-length") {
403 const auto size = strtol(value.c_str(), &end, 10);
404 if (errno != ERANGE && end == value.c_str() + value.size()) {
405 estimated_size_ = size;
411 if (length == 2 && str[0] ==
'\r' && str[1] ==
'\n') {
415 parsing_headers_ =
false;
420 std::unique_lock<Mutex> lock(mutex_);
421 const size_t remaining = upload_data_.
size() - upload_pos_;
422 if (length > remaining)
424 std::memcpy(buffer, upload_data_.
data() + upload_pos_, length);
425 upload_pos_ += length;
429 void XMLHttpRequest::Reset() {
439 last_progress_time_ = 0;
441 parsing_headers_ =
false;
442 abort_pending_ =
false;
444 response_headers_.clear();
446 upload_data_.
Clear();
448 curl_easy_reset(curl_);
449 curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, DownloadCallback);
450 curl_easy_setopt(curl_, CURLOPT_WRITEDATA,
this);
451 curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, HeaderCallback);
452 curl_easy_setopt(curl_, CURLOPT_HEADERDATA,
this);
453 curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L);
454 curl_easy_setopt(curl_, CURLOPT_USERAGENT,
USER_AGENT);
456 const std::string cookie_file =
458 curl_easy_setopt(curl_, CURLOPT_COOKIEFILE, cookie_file.c_str());
459 curl_easy_setopt(curl_, CURLOPT_COOKIEJAR, cookie_file.c_str());
462 curl_easy_setopt(curl_, CURLOPT_TCP_NODELAY, 1L);
464 curl_easy_setopt(curl_, CURLOPT_EXPECT_100_TIMEOUT_MS, 1L);
466 if (request_headers_)
467 curl_slist_free_all(request_headers_);
468 request_headers_ =
nullptr;
471 void XMLHttpRequest::OnRequestComplete(CURLcode code) {
473 std::unique_lock<Mutex> lock(mutex_);
474 if (code == CURLE_OK) {
480 curl_easy_getinfo(curl_, CURLINFO_EFFECTIVE_URL, &url);
484 curl_easy_setopt(curl_, CURLOPT_COOKIELIST,
"FLUSH");
494 if (!abort_pending_) {
496 ScheduleEvent<events::Event>(EventType::ReadyStateChange);
498 double total_size = CurrentDownloadSize(curl_);
499 ScheduleEvent<events::ProgressEvent>(EventType::Progress,
true, total_size,
503 ScheduleEvent<events::Event>(EventType::Load);
505 case CURLE_OPERATION_TIMEDOUT:
506 ScheduleEvent<events::Event>(EventType::Timeout);
509 LOG(ERROR) <<
"Error returned by curl: " << code;
510 ScheduleEvent<events::Event>(EventType::Error);
513 ScheduleEvent<events::ProgressEvent>(EventType::LoadEnd,
true, total_size,
548 AddMemberFunction(
"getAllResponseHeaders",
void SetFromDynamicBuffer(const util::DynamicBuffer &other)
std::string ToAsciiLower(const std::string &source)
bool ContainsRequest(RefPtr< js::XMLHttpRequest > request) const
std::string GetPathForDynamicFile(const std::string &file) const
std::string GetAllResponseHeaders() const
const uint8_t * data() const
bool WithCredentials() const
const T & value() const &
Listener on_ready_state_change
void Trace(memory::HeapTracer *tracer) const override
std::string response_text
void Trace(const Traceable *ptr)
std::string CreateString() const
optional< std::string > GetResponseHeader(const std::string &name) const
void SetFromBuffer(const void *buffer, size_t size)
void AbortRequest(RefPtr< js::XMLHttpRequest > request)
static JsManagerImpl * Instance()
NetworkThread * NetworkThread()
void OnHeaderReceived(const uint8_t *buffer, size_t length)
void AppendCopy(const void *buffer, size_t size)
static const Clock Instance
ExceptionOr< void > Open(const std::string &method, const std::string &url, optional< bool > async, optional< std::string > user, optional< std::string > password)
void AddRequest(RefPtr< js::XMLHttpRequest > request)
std::shared_ptr< ThreadEvent< impl::RetOf< Func > > > AddInternalTask(TaskPriority priority, const std::string &name, Func &&callback)
bool IsShortLived() const override
std::string TrimAsciiWhitespace(const std::string &source)
static JsError DOMException(ExceptionCode code)
virtual uint64_t GetMonotonicTime() const
ExceptionOr< void > SetWithCredentials(bool with_credentials)
std::string response_type
ExceptionOr< void > Send(optional< variant< ByteBuffer, ByteString >> maybe_data)
void AddListenerField(EventType type, Listener *on_field)
void OnDataReceived(uint8_t *buffer, size_t length)
size_t OnUpload(uint8_t *buffer, size_t length)
ExceptionOr< void > SetRequestHeader(const std::string &key, const std::string &value)
TaskRunner * MainThread()