]>
Commit | Line | Data |
---|---|---|
d6fdeb41 | 1 | /* |
f6e9a3ee | 2 | * Copyright (C) 1996-2019 The Squid Software Foundation and contributors |
d6fdeb41 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
9 | #include "squid.h" | |
10 | #include "client_side_request.h" | |
d3dddfb5 | 11 | #include "http/Stream.h" |
d6fdeb41 AJ |
12 | #include "HttpHdrContRange.h" |
13 | #include "HttpHeaderTools.h" | |
bc8628c7 | 14 | #include "Store.h" |
d6fdeb41 | 15 | #include "TimeOrTag.h" |
b27668ec EB |
16 | #if USE_DELAY_POOLS |
17 | #include "acl/FilledChecklist.h" | |
18 | #include "ClientInfo.h" | |
19 | #include "fde.h" | |
20 | #include "MessageDelayPools.h" | |
21 | #endif | |
d6fdeb41 | 22 | |
d3dddfb5 | 23 | Http::Stream::Stream(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) : |
d6fdeb41 AJ |
24 | clientConnection(aConn), |
25 | http(aReq), | |
26 | reply(nullptr), | |
27 | writtenToSocket(0), | |
28 | mayUseConnection_(false), | |
942b1c39 | 29 | connRegistered_(false) |
d6fdeb41 AJ |
30 | { |
31 | assert(http != nullptr); | |
942b1c39 | 32 | memset(reqbuf, '\0', sizeof (reqbuf)); |
d6fdeb41 AJ |
33 | flags.deferred = 0; |
34 | flags.parsed_ok = 0; | |
35 | deferredparams.node = nullptr; | |
36 | deferredparams.rep = nullptr; | |
37 | } | |
38 | ||
d3dddfb5 | 39 | Http::Stream::~Stream() |
d6fdeb41 AJ |
40 | { |
41 | if (auto node = getTail()) { | |
d3dddfb5 | 42 | if (auto ctx = dynamic_cast<Http::Stream *>(node->data.getRaw())) { |
d6fdeb41 AJ |
43 | /* We are *always* the tail - prevent recursive free */ |
44 | assert(this == ctx); | |
45 | node->data = nullptr; | |
46 | } | |
47 | } | |
48 | httpRequestFree(http); | |
49 | } | |
50 | ||
51 | void | |
d3dddfb5 | 52 | Http::Stream::registerWithConn() |
d6fdeb41 AJ |
53 | { |
54 | assert(!connRegistered_); | |
55 | assert(getConn()); | |
56 | connRegistered_ = true; | |
d3dddfb5 | 57 | getConn()->pipeline.add(Http::StreamPointer(this)); |
d6fdeb41 AJ |
58 | } |
59 | ||
60 | bool | |
d3dddfb5 | 61 | Http::Stream::startOfOutput() const |
d6fdeb41 AJ |
62 | { |
63 | return http->out.size == 0; | |
64 | } | |
65 | ||
66 | void | |
d3dddfb5 | 67 | Http::Stream::writeComplete(size_t size) |
d6fdeb41 AJ |
68 | { |
69 | const StoreEntry *entry = http->storeEntry(); | |
70 | debugs(33, 5, clientConnection << ", sz " << size << | |
71 | ", off " << (http->out.size + size) << ", len " << | |
72 | (entry ? entry->objectLen() : 0)); | |
73 | ||
74 | http->out.size += size; | |
75 | ||
76 | if (clientHttpRequestStatus(clientConnection->fd, http)) { | |
77 | initiateClose("failure or true request status"); | |
78 | /* Do we leak here ? */ | |
79 | return; | |
80 | } | |
81 | ||
82 | switch (socketState()) { | |
83 | ||
84 | case STREAM_NONE: | |
85 | pullData(); | |
86 | break; | |
87 | ||
88 | case STREAM_COMPLETE: { | |
89 | debugs(33, 5, clientConnection << " Stream complete, keepalive is " << | |
abc18020 | 90 | http->request->flags.proxyKeepalive); |
d6fdeb41 AJ |
91 | ConnStateData *c = getConn(); |
92 | if (!http->request->flags.proxyKeepalive) | |
93 | clientConnection->close(); | |
94 | finished(); | |
95 | c->kick(); | |
96 | } | |
97 | return; | |
98 | ||
99 | case STREAM_UNPLANNED_COMPLETE: | |
100 | initiateClose("STREAM_UNPLANNED_COMPLETE"); | |
101 | return; | |
102 | ||
103 | case STREAM_FAILED: | |
104 | initiateClose("STREAM_FAILED"); | |
105 | return; | |
106 | ||
107 | default: | |
d3dddfb5 | 108 | fatal("Hit unreachable code in Http::Stream::writeComplete\n"); |
d6fdeb41 AJ |
109 | } |
110 | } | |
111 | ||
112 | void | |
d3dddfb5 | 113 | Http::Stream::pullData() |
d6fdeb41 AJ |
114 | { |
115 | debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection); | |
116 | ||
117 | /* More data will be coming from the stream. */ | |
942b1c39 | 118 | StoreIOBuffer readBuffer; |
d6fdeb41 AJ |
119 | /* XXX: Next requested byte in the range sequence */ |
120 | /* XXX: length = getmaximumrangelenfgth */ | |
121 | readBuffer.offset = getNextRangeOffset(); | |
942b1c39 AJ |
122 | readBuffer.length = HTTP_REQBUF_SZ; |
123 | readBuffer.data = reqbuf; | |
d6fdeb41 AJ |
124 | /* we may note we have reached the end of the wanted ranges */ |
125 | clientStreamRead(getTail(), http, readBuffer); | |
126 | } | |
127 | ||
128 | bool | |
d3dddfb5 | 129 | Http::Stream::multipartRangeRequest() const |
d6fdeb41 AJ |
130 | { |
131 | return http->multipartRangeRequest(); | |
132 | } | |
133 | ||
134 | int64_t | |
d3dddfb5 | 135 | Http::Stream::getNextRangeOffset() const |
d6fdeb41 AJ |
136 | { |
137 | debugs (33, 5, "range: " << http->request->range << | |
138 | "; http offset " << http->out.offset << | |
139 | "; reply " << reply); | |
140 | ||
141 | // XXX: This method is called from many places, including pullData() which | |
142 | // may be called before prepareReply() [on some Squid-generated errors]. | |
143 | // Hence, we may not even know yet whether we should honor/do ranges. | |
144 | ||
145 | if (http->request->range) { | |
146 | /* offset in range specs does not count the prefix of an http msg */ | |
147 | /* check: reply was parsed and range iterator was initialized */ | |
148 | assert(http->range_iter.valid); | |
149 | /* filter out data according to range specs */ | |
150 | assert(canPackMoreRanges()); | |
151 | { | |
152 | assert(http->range_iter.currentSpec()); | |
153 | /* offset of still missing data */ | |
154 | int64_t start = http->range_iter.currentSpec()->offset + | |
155 | http->range_iter.currentSpec()->length - | |
156 | http->range_iter.debt(); | |
157 | debugs(33, 3, "clientPackMoreRanges: in: offset: " << http->out.offset); | |
158 | debugs(33, 3, "clientPackMoreRanges: out:" | |
159 | " start: " << start << | |
160 | " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" << | |
161 | " [" << http->range_iter.currentSpec()->offset << | |
162 | ", " << http->range_iter.currentSpec()->offset + | |
163 | http->range_iter.currentSpec()->length << ")," | |
164 | " len: " << http->range_iter.currentSpec()->length << | |
165 | " debt: " << http->range_iter.debt()); | |
166 | if (http->range_iter.currentSpec()->length != -1) | |
167 | assert(http->out.offset <= start); /* we did not miss it */ | |
168 | ||
169 | return start; | |
170 | } | |
171 | ||
8341f96d | 172 | } else if (reply && reply->contentRange()) { |
d6fdeb41 AJ |
173 | /* request does not have ranges, but reply does */ |
174 | /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range | |
175 | * becomes HttpHdrRange rather than HttpHdrRangeSpec. | |
176 | */ | |
8341f96d | 177 | return http->out.offset + reply->contentRange()->spec.offset; |
d6fdeb41 AJ |
178 | } |
179 | ||
180 | return http->out.offset; | |
181 | } | |
182 | ||
183 | /** | |
184 | * increments iterator "i" | |
185 | * used by clientPackMoreRanges | |
186 | * | |
187 | * \retval true there is still data available to pack more ranges | |
188 | * \retval false | |
189 | */ | |
190 | bool | |
d3dddfb5 | 191 | Http::Stream::canPackMoreRanges() const |
d6fdeb41 AJ |
192 | { |
193 | /** first update iterator "i" if needed */ | |
194 | if (!http->range_iter.debt()) { | |
195 | debugs(33, 5, "At end of current range spec for " << clientConnection); | |
196 | ||
197 | if (http->range_iter.pos != http->range_iter.end) | |
198 | ++http->range_iter.pos; | |
199 | ||
200 | http->range_iter.updateSpec(); | |
201 | } | |
202 | ||
203 | assert(!http->range_iter.debt() == !http->range_iter.currentSpec()); | |
204 | ||
205 | /* paranoid sync condition */ | |
206 | /* continue condition: need_more_data */ | |
207 | debugs(33, 5, "returning " << (http->range_iter.currentSpec() ? true : false)); | |
208 | return http->range_iter.currentSpec() ? true : false; | |
209 | } | |
210 | ||
211 | /// Adapt stream status to account for Range cases | |
212 | clientStream_status_t | |
d3dddfb5 | 213 | Http::Stream::socketState() |
d6fdeb41 AJ |
214 | { |
215 | switch (clientStreamStatus(getTail(), http)) { | |
216 | ||
217 | case STREAM_NONE: | |
218 | /* check for range support ending */ | |
219 | if (http->request->range) { | |
220 | /* check: reply was parsed and range iterator was initialized */ | |
221 | assert(http->range_iter.valid); | |
222 | /* filter out data according to range specs */ | |
223 | ||
224 | if (!canPackMoreRanges()) { | |
225 | debugs(33, 5, "Range request at end of returnable " << | |
226 | "range sequence on " << clientConnection); | |
227 | // we got everything we wanted from the store | |
228 | return STREAM_COMPLETE; | |
229 | } | |
8341f96d | 230 | } else if (reply && reply->contentRange()) { |
d6fdeb41 AJ |
231 | /* reply has content-range, but Squid is not managing ranges */ |
232 | const int64_t &bytesSent = http->out.offset; | |
8341f96d | 233 | const int64_t &bytesExpected = reply->contentRange()->spec.length; |
d6fdeb41 AJ |
234 | |
235 | debugs(33, 7, "body bytes sent vs. expected: " << | |
236 | bytesSent << " ? " << bytesExpected << " (+" << | |
8341f96d | 237 | reply->contentRange()->spec.offset << ")"); |
d6fdeb41 AJ |
238 | |
239 | // did we get at least what we expected, based on range specs? | |
240 | ||
241 | if (bytesSent == bytesExpected) // got everything | |
242 | return STREAM_COMPLETE; | |
243 | ||
244 | if (bytesSent > bytesExpected) // Error: Sent more than expected | |
245 | return STREAM_UNPLANNED_COMPLETE; | |
246 | } | |
247 | ||
248 | return STREAM_NONE; | |
249 | ||
250 | case STREAM_COMPLETE: | |
251 | return STREAM_COMPLETE; | |
252 | ||
253 | case STREAM_UNPLANNED_COMPLETE: | |
254 | return STREAM_UNPLANNED_COMPLETE; | |
255 | ||
256 | case STREAM_FAILED: | |
257 | return STREAM_FAILED; | |
258 | } | |
259 | ||
260 | fatal ("unreachable code\n"); | |
261 | return STREAM_NONE; | |
262 | } | |
263 | ||
264 | void | |
d3dddfb5 | 265 | Http::Stream::sendStartOfMessage(HttpReply *rep, StoreIOBuffer bodyData) |
d6fdeb41 AJ |
266 | { |
267 | prepareReply(rep); | |
268 | assert(rep); | |
269 | MemBuf *mb = rep->pack(); | |
270 | ||
61beade2 | 271 | // dump now, so we do not output any body. |
d6fdeb41 AJ |
272 | debugs(11, 2, "HTTP Client " << clientConnection); |
273 | debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------"); | |
274 | ||
275 | /* Save length of headers for persistent conn checks */ | |
276 | http->out.headers_sz = mb->contentSize(); | |
277 | #if HEADERS_LOG | |
278 | headersLog(0, 0, http->request->method, rep); | |
279 | #endif | |
280 | ||
281 | if (bodyData.data && bodyData.length) { | |
282 | if (multipartRangeRequest()) | |
283 | packRange(bodyData, mb); | |
284 | else if (http->request->flags.chunkedReply) { | |
285 | packChunk(bodyData, *mb); | |
286 | } else { | |
287 | size_t length = lengthToSend(bodyData.range()); | |
288 | noteSentBodyBytes(length); | |
289 | mb->append(bodyData.data, length); | |
290 | } | |
291 | } | |
b27668ec EB |
292 | #if USE_DELAY_POOLS |
293 | for (const auto &pool: MessageDelayPools::Instance()->pools) { | |
294 | if (pool->access) { | |
295 | std::unique_ptr<ACLFilledChecklist> chl(clientAclChecklistCreate(pool->access, http)); | |
296 | chl->reply = rep; | |
297 | HTTPMSGLOCK(chl->reply); | |
298 | const allow_t answer = chl->fastCheck(); | |
06bf5384 | 299 | if (answer.allowed()) { |
b27668ec EB |
300 | writeQuotaHandler = pool->createBucket(); |
301 | fd_table[clientConnection->fd].writeQuotaHandler = writeQuotaHandler; | |
302 | break; | |
303 | } else { | |
304 | debugs(83, 4, "Response delay pool " << pool->poolName << | |
305 | " skipped because ACL " << answer); | |
306 | } | |
307 | } | |
308 | } | |
309 | #endif | |
d6fdeb41 AJ |
310 | |
311 | getConn()->write(mb); | |
312 | delete mb; | |
313 | } | |
314 | ||
315 | void | |
d3dddfb5 | 316 | Http::Stream::sendBody(StoreIOBuffer bodyData) |
d6fdeb41 AJ |
317 | { |
318 | if (!multipartRangeRequest() && !http->request->flags.chunkedReply) { | |
319 | size_t length = lengthToSend(bodyData.range()); | |
320 | noteSentBodyBytes(length); | |
321 | getConn()->write(bodyData.data, length); | |
322 | return; | |
323 | } | |
324 | ||
325 | MemBuf mb; | |
326 | mb.init(); | |
327 | if (multipartRangeRequest()) | |
328 | packRange(bodyData, &mb); | |
329 | else | |
330 | packChunk(bodyData, mb); | |
331 | ||
332 | if (mb.contentSize()) | |
333 | getConn()->write(&mb); | |
334 | else | |
335 | writeComplete(0); | |
336 | } | |
337 | ||
338 | size_t | |
d3dddfb5 | 339 | Http::Stream::lengthToSend(Range<int64_t> const &available) const |
d6fdeb41 AJ |
340 | { |
341 | // the size of available range can always fit into a size_t type | |
342 | size_t maximum = available.size(); | |
343 | ||
344 | if (!http->request->range) | |
345 | return maximum; | |
346 | ||
347 | assert(canPackMoreRanges()); | |
348 | ||
349 | if (http->range_iter.debt() == -1) | |
350 | return maximum; | |
351 | ||
352 | assert(http->range_iter.debt() > 0); | |
353 | ||
354 | /* TODO this + the last line could be a range intersection calculation */ | |
355 | if (available.start < http->range_iter.currentSpec()->offset) | |
356 | return 0; | |
357 | ||
358 | return min(http->range_iter.debt(), static_cast<int64_t>(maximum)); | |
359 | } | |
360 | ||
361 | void | |
d3dddfb5 | 362 | Http::Stream::noteSentBodyBytes(size_t bytes) |
d6fdeb41 AJ |
363 | { |
364 | debugs(33, 7, bytes << " body bytes"); | |
365 | http->out.offset += bytes; | |
366 | ||
367 | if (!http->request->range) | |
368 | return; | |
369 | ||
370 | if (http->range_iter.debt() != -1) { | |
371 | http->range_iter.debt(http->range_iter.debt() - bytes); | |
372 | assert (http->range_iter.debt() >= 0); | |
373 | } | |
374 | ||
375 | /* debt() always stops at -1, below that is a bug */ | |
376 | assert(http->range_iter.debt() >= -1); | |
377 | } | |
378 | ||
379 | /// \return true when If-Range specs match reply, false otherwise | |
380 | static bool | |
381 | clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep) | |
382 | { | |
383 | const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE); | |
384 | ||
385 | /* check for parsing falure */ | |
386 | if (!spec.valid) | |
387 | return false; | |
388 | ||
389 | /* got an ETag? */ | |
390 | if (spec.tag.str) { | |
391 | ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG); | |
392 | debugs(33, 3, "ETags: " << spec.tag.str << " and " << | |
393 | (rep_tag.str ? rep_tag.str : "<none>")); | |
394 | ||
395 | if (!rep_tag.str) | |
396 | return false; // entity has no etag to compare with! | |
397 | ||
398 | if (spec.tag.weak || rep_tag.weak) { | |
399 | debugs(33, DBG_IMPORTANT, "Weak ETags are not allowed in If-Range: " << | |
400 | spec.tag.str << " ? " << rep_tag.str); | |
401 | return false; // must use strong validator for sub-range requests | |
402 | } | |
403 | ||
404 | return etagIsStrongEqual(rep_tag, spec.tag); | |
405 | } | |
406 | ||
407 | /* got modification time? */ | |
408 | if (spec.time >= 0) | |
438b41ba | 409 | return !http->storeEntry()->modifiedSince(spec.time); |
d6fdeb41 AJ |
410 | |
411 | assert(0); /* should not happen */ | |
412 | return false; | |
413 | } | |
414 | ||
415 | // seems to be something better suited to Server logic | |
416 | /** adds appropriate Range headers if needed */ | |
417 | void | |
d3dddfb5 | 418 | Http::Stream::buildRangeHeader(HttpReply *rep) |
d6fdeb41 AJ |
419 | { |
420 | HttpHeader *hdr = rep ? &rep->header : nullptr; | |
421 | const char *range_err = nullptr; | |
422 | HttpRequest *request = http->request; | |
423 | assert(request->range); | |
424 | /* check if we still want to do ranges */ | |
425 | int64_t roffLimit = request->getRangeOffsetLimit(); | |
8341f96d | 426 | auto contentRange = rep ? rep->contentRange() : nullptr; |
d6fdeb41 AJ |
427 | |
428 | if (!rep) | |
429 | range_err = "no [parse-able] reply"; | |
430 | else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent)) | |
431 | range_err = "wrong status code"; | |
8341f96d EB |
432 | else if (rep->sline.status() == Http::scPartialContent) |
433 | range_err = "too complex response"; // probably contains what the client needs | |
434 | else if (rep->sline.status() != Http::scOkay) | |
435 | range_err = "wrong status code"; | |
436 | else if (hdr->has(Http::HdrType::CONTENT_RANGE)) { | |
437 | Must(!contentRange); // this is a 200, not 206 response | |
438 | range_err = "meaningless response"; // the status code or the header is wrong | |
439 | } | |
d6fdeb41 AJ |
440 | else if (rep->content_length < 0) |
441 | range_err = "unknown length"; | |
442 | else if (rep->content_length != http->memObject()->getReply()->content_length) | |
443 | range_err = "INCONSISTENT length"; /* a bug? */ | |
444 | ||
445 | /* hits only - upstream CachePeer determines correct behaviour on misses, | |
446 | * and client_side_reply determines hits candidates | |
447 | */ | |
448 | else if (http->logType.isTcpHit() && | |
449 | http->request->header.has(Http::HdrType::IF_RANGE) && | |
450 | !clientIfRangeMatch(http, rep)) | |
451 | range_err = "If-Range match failed"; | |
452 | ||
453 | else if (!http->request->range->canonize(rep)) | |
454 | range_err = "canonization failed"; | |
455 | else if (http->request->range->isComplex()) | |
456 | range_err = "too complex range header"; | |
457 | else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit)) | |
458 | range_err = "range outside range_offset_limit"; | |
459 | ||
460 | /* get rid of our range specs on error */ | |
461 | if (range_err) { | |
462 | /* XXX We do this here because we need canonisation etc. However, this current | |
463 | * code will lead to incorrect store offset requests - the store will have the | |
464 | * offset data, but we won't be requesting it. | |
465 | * So, we can either re-request, or generate an error | |
466 | */ | |
467 | http->request->ignoreRange(range_err); | |
468 | } else { | |
469 | /* XXX: TODO: Review, this unconditional set may be wrong. */ | |
470 | rep->sline.set(rep->sline.version, Http::scPartialContent); | |
471 | // web server responded with a valid, but unexpected range. | |
472 | // will (try-to) forward as-is. | |
473 | //TODO: we should cope with multirange request/responses | |
8341f96d EB |
474 | // TODO: review, since rep->content_range is always nil here. |
475 | bool replyMatchRequest = contentRange != nullptr ? | |
476 | request->range->contains(contentRange->spec) : | |
d6fdeb41 AJ |
477 | true; |
478 | const int spec_count = http->request->range->specs.size(); | |
479 | int64_t actual_clen = -1; | |
480 | ||
481 | debugs(33, 3, "range spec count: " << spec_count << | |
482 | " virgin clen: " << rep->content_length); | |
483 | assert(spec_count > 0); | |
484 | /* append appropriate header(s) */ | |
485 | if (spec_count == 1) { | |
486 | if (!replyMatchRequest) { | |
8341f96d | 487 | hdr->putContRange(contentRange); |
d6fdeb41 AJ |
488 | actual_clen = rep->content_length; |
489 | //http->range_iter.pos = rep->content_range->spec.begin(); | |
8341f96d EB |
490 | (*http->range_iter.pos)->offset = contentRange->spec.offset; |
491 | (*http->range_iter.pos)->length = contentRange->spec.length; | |
d6fdeb41 AJ |
492 | |
493 | } else { | |
494 | HttpHdrRange::iterator pos = http->request->range->begin(); | |
495 | assert(*pos); | |
496 | /* append Content-Range */ | |
497 | ||
8341f96d | 498 | if (!contentRange) { |
d6fdeb41 AJ |
499 | /* No content range, so this was a full object we are |
500 | * sending parts of. | |
501 | */ | |
502 | httpHeaderAddContRange(hdr, **pos, rep->content_length); | |
503 | } | |
504 | ||
505 | /* set new Content-Length to the actual number of bytes | |
506 | * transmitted in the message-body */ | |
507 | actual_clen = (*pos)->length; | |
508 | } | |
509 | } else { | |
510 | /* multipart! */ | |
511 | /* generate boundary string */ | |
512 | http->range_iter.boundary = http->rangeBoundaryStr(); | |
513 | /* delete old Content-Type, add ours */ | |
514 | hdr->delById(Http::HdrType::CONTENT_TYPE); | |
515 | httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE, | |
516 | "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"", | |
517 | SQUIDSTRINGPRINT(http->range_iter.boundary)); | |
518 | /* Content-Length is not required in multipart responses | |
519 | * but it is always nice to have one */ | |
520 | actual_clen = http->mRangeCLen(); | |
521 | ||
522 | /* http->out needs to start where we want data at */ | |
523 | http->out.offset = http->range_iter.currentSpec()->offset; | |
524 | } | |
525 | ||
526 | /* replace Content-Length header */ | |
527 | assert(actual_clen >= 0); | |
528 | hdr->delById(Http::HdrType::CONTENT_LENGTH); | |
529 | hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen); | |
530 | debugs(33, 3, "actual content length: " << actual_clen); | |
531 | ||
532 | /* And start the range iter off */ | |
533 | http->range_iter.updateSpec(); | |
534 | } | |
535 | } | |
536 | ||
537 | clientStreamNode * | |
d3dddfb5 | 538 | Http::Stream::getTail() const |
d6fdeb41 AJ |
539 | { |
540 | if (http->client_stream.tail) | |
541 | return static_cast<clientStreamNode *>(http->client_stream.tail->data); | |
542 | ||
543 | return nullptr; | |
544 | } | |
545 | ||
546 | clientStreamNode * | |
d3dddfb5 | 547 | Http::Stream::getClientReplyContext() const |
d6fdeb41 AJ |
548 | { |
549 | return static_cast<clientStreamNode *>(http->client_stream.tail->prev->data); | |
550 | } | |
551 | ||
552 | ConnStateData * | |
d3dddfb5 | 553 | Http::Stream::getConn() const |
d6fdeb41 AJ |
554 | { |
555 | assert(http && http->getConn()); | |
556 | return http->getConn(); | |
557 | } | |
558 | ||
559 | /// remembers the abnormal connection termination for logging purposes | |
560 | void | |
d3dddfb5 | 561 | Http::Stream::noteIoError(const int xerrno) |
d6fdeb41 AJ |
562 | { |
563 | if (http) { | |
564 | http->logType.err.timedout = (xerrno == ETIMEDOUT); | |
565 | // aborted even if xerrno is zero (which means read abort/eof) | |
566 | http->logType.err.aborted = (xerrno != ETIMEDOUT); | |
567 | } | |
568 | } | |
569 | ||
570 | void | |
d3dddfb5 | 571 | Http::Stream::finished() |
d6fdeb41 AJ |
572 | { |
573 | ConnStateData *conn = getConn(); | |
574 | ||
575 | /* we can't handle any more stream data - detach */ | |
576 | clientStreamDetach(getTail(), http); | |
577 | ||
578 | assert(connRegistered_); | |
579 | connRegistered_ = false; | |
d3dddfb5 | 580 | conn->pipeline.popMe(Http::StreamPointer(this)); |
d6fdeb41 AJ |
581 | } |
582 | ||
583 | /// called when we encounter a response-related error | |
584 | void | |
d3dddfb5 | 585 | Http::Stream::initiateClose(const char *reason) |
d6fdeb41 AJ |
586 | { |
587 | debugs(33, 4, clientConnection << " because " << reason); | |
588 | getConn()->stopSending(reason); // closes ASAP | |
589 | } | |
590 | ||
591 | void | |
d3dddfb5 | 592 | Http::Stream::deferRecipientForLater(clientStreamNode *node, HttpReply *rep, StoreIOBuffer receivedData) |
d6fdeb41 AJ |
593 | { |
594 | debugs(33, 2, "Deferring request " << http->uri); | |
595 | assert(flags.deferred == 0); | |
596 | flags.deferred = 1; | |
597 | deferredparams.node = node; | |
598 | deferredparams.rep = rep; | |
599 | deferredparams.queuedBuffer = receivedData; | |
600 | } | |
601 | ||
602 | void | |
d3dddfb5 | 603 | Http::Stream::prepareReply(HttpReply *rep) |
d6fdeb41 AJ |
604 | { |
605 | reply = rep; | |
606 | if (http->request->range) | |
607 | buildRangeHeader(rep); | |
608 | } | |
609 | ||
610 | /** | |
611 | * Packs bodyData into mb using chunked encoding. | |
612 | * Packs the last-chunk if bodyData is empty. | |
613 | */ | |
614 | void | |
d3dddfb5 | 615 | Http::Stream::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb) |
d6fdeb41 AJ |
616 | { |
617 | const uint64_t length = | |
618 | static_cast<uint64_t>(lengthToSend(bodyData.range())); | |
619 | noteSentBodyBytes(length); | |
620 | ||
621 | mb.appendf("%" PRIX64 "\r\n", length); | |
622 | mb.append(bodyData.data, length); | |
623 | mb.append("\r\n", 2); | |
624 | } | |
625 | ||
626 | /** | |
627 | * extracts a "range" from *buf and appends them to mb, updating | |
628 | * all offsets and such. | |
629 | */ | |
630 | void | |
d3dddfb5 | 631 | Http::Stream::packRange(StoreIOBuffer const &source, MemBuf *mb) |
d6fdeb41 AJ |
632 | { |
633 | HttpHdrRangeIter * i = &http->range_iter; | |
634 | Range<int64_t> available(source.range()); | |
635 | char const *buf = source.data; | |
636 | ||
637 | while (i->currentSpec() && available.size()) { | |
638 | const size_t copy_sz = lengthToSend(available); | |
639 | if (copy_sz) { | |
640 | // intersection of "have" and "need" ranges must not be empty | |
641 | assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length); | |
642 | assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset); | |
643 | ||
644 | /* | |
645 | * put boundary and headers at the beginning of a range in a | |
646 | * multi-range | |
647 | */ | |
648 | if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) { | |
649 | assert(http->memObject()); | |
650 | clientPackRangeHdr( | |
651 | http->memObject()->getReply(), /* original reply */ | |
652 | i->currentSpec(), /* current range */ | |
653 | i->boundary, /* boundary, the same for all */ | |
654 | mb); | |
655 | } | |
656 | ||
657 | // append content | |
658 | debugs(33, 3, "appending " << copy_sz << " bytes"); | |
659 | noteSentBodyBytes(copy_sz); | |
660 | mb->append(buf, copy_sz); | |
661 | ||
662 | // update offsets | |
663 | available.start += copy_sz; | |
664 | buf += copy_sz; | |
665 | } | |
666 | ||
667 | if (!canPackMoreRanges()) { | |
668 | debugs(33, 3, "Returning because !canPackMoreRanges."); | |
669 | if (i->debt() == 0) | |
670 | // put terminating boundary for multiparts | |
671 | clientPackTermBound(i->boundary, mb); | |
672 | return; | |
673 | } | |
674 | ||
675 | int64_t nextOffset = getNextRangeOffset(); | |
676 | assert(nextOffset >= http->out.offset); | |
677 | int64_t skip = nextOffset - http->out.offset; | |
678 | /* adjust for not to be transmitted bytes */ | |
679 | http->out.offset = nextOffset; | |
680 | ||
681 | if (available.size() <= (uint64_t)skip) | |
682 | return; | |
683 | ||
684 | available.start += skip; | |
685 | buf += skip; | |
686 | ||
687 | if (copy_sz == 0) | |
688 | return; | |
689 | } | |
690 | } | |
691 | ||
692 | void | |
d3dddfb5 | 693 | Http::Stream::doClose() |
d6fdeb41 AJ |
694 | { |
695 | clientConnection->close(); | |
696 | } | |
697 |