2
* Copyright (C) 2010, 2011 Tuomo Penttinen, all rights reserved.
4
* Author: Tuomo Penttinen <tp@herqq.org>
6
* This file is part of Herqq UPnP (HUPnP) library.
8
* Herqq UPnP is free software: you can redistribute it and/or modify
9
* it under the terms of the GNU Lesser General Public License as published by
10
* the Free Software Foundation, either version 3 of the License, or
11
* (at your option) any later version.
13
* Herqq UPnP is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU Lesser General Public License for more details.
18
* You should have received a copy of the GNU Lesser General Public License
19
* along with Herqq UPnP. If not, see <http://www.gnu.org/licenses/>.
22
#include "hhttp_asynchandler_p.h"
23
#include "hhttp_messagecreator_p.h"
24
#include "hhttp_utils_p.h"
26
#include "../general/hupnp_global_p.h"
27
#include "../devicehosting/messages/hevent_messages_p.h"
29
#include <QtNetwork/QTcpSocket>
31
#include <QtSoapMessage>
39
HHttpAsyncOperation::HHttpAsyncOperation(
40
const QByteArray& loggingIdentifier, unsigned int id, HMessagingInfo* mi,
41
bool waitingRequest, QObject* parent) :
47
m_state(Internal_NotStarted),
52
m_loggingIdentifier(loggingIdentifier),
53
m_opType(waitingRequest ? ReceiveRequest : ReceiveResponse)
56
&m_mi->socket(), SIGNAL(readyRead()), this, SLOT(readyRead()));
58
Q_ASSERT(ok); Q_UNUSED(ok)
61
&m_mi->socket(), SIGNAL(error(QAbstractSocket::SocketError)),
62
this, SLOT(error(QAbstractSocket::SocketError)));
67
HHttpAsyncOperation::HHttpAsyncOperation(
68
const QByteArray& loggingIdentifier, unsigned int id, HMessagingInfo* mi,
69
const QByteArray& data, bool sendOnly, QObject* parent) :
75
m_state(Internal_NotStarted),
80
m_loggingIdentifier(loggingIdentifier),
81
m_opType(sendOnly ? SendOnly : MsgIO)
84
&m_mi->socket(), SIGNAL(bytesWritten(qint64)),
85
this, SLOT(bytesWritten(qint64)));
87
Q_ASSERT(ok); Q_UNUSED(ok)
90
&m_mi->socket(), SIGNAL(readyRead()), this, SLOT(readyRead()));
95
&m_mi->socket(), SIGNAL(error(QAbstractSocket::SocketError)),
96
this, SLOT(error(QAbstractSocket::SocketError)));
101
HHttpAsyncOperation::~HHttpAsyncOperation()
107
void HHttpAsyncOperation::sendChunked()
109
static const char crlf[] = {"\r\n"};
111
// then start sending the data in chunks
112
qint64 bytesWritten = 0;
114
if (m_dataSent < m_dataToSend.size())
116
qint32 dataToSendSize =
117
m_dataSend > 0 ? m_dataSend :
118
qMin(m_dataToSend.size() - m_dataSent,
119
static_cast<qint64>(m_mi->chunkedInfo().max()));
121
if (m_state == Internal_WritingChunkedSizeLine)
123
// write the size line of the next chunk
125
sizeLine.setNum(dataToSendSize, 16);
126
sizeLine.append(crlf, 2);
128
bytesWritten = m_mi->socket().write(sizeLine);
129
if (bytesWritten != sizeLine.size())
131
m_mi->setLastErrorDescription("failed to send chunked data");
132
done_(Internal_Failed);
136
m_state = Internal_WritingChunk;
141
m_mi->socket().write(m_dataToSend.data() + m_dataSent, dataToSendSize);
143
if (bytesWritten < 0)
145
m_mi->setLastErrorDescription("failed to send chunked data");
146
done_(Internal_Failed);
150
m_dataSent += bytesWritten;
152
if (bytesWritten != dataToSendSize)
154
m_dataSend = dataToSendSize - bytesWritten;
156
// wait for bytesWritten() and then attempt to send the data remaining
165
// and after the chunk, write the trailing crlf and start again if there's
167
bytesWritten = m_mi->socket().write(crlf, 2);
168
if (bytesWritten != 2)
170
m_mi->setLastErrorDescription("failed to send chunked data");
171
done_(Internal_Failed);
175
m_state = Internal_WritingChunkedSizeLine;
178
if (m_dataSent >= m_dataToSend.size())
180
// write the "eof" == zero + crlf
181
const char eof[] = "0\r\n";
182
m_mi->socket().write(&eof[0], 3);
183
m_mi->socket().flush();
185
if (m_opType == SendOnly)
187
done_(Internal_FinishedSuccessfully);
191
m_state = Internal_ReadingHeader;
195
void HHttpAsyncOperation::readBlob()
197
QByteArray buf; buf.resize(m_dataToRead+1);
200
qint64 retVal = m_mi->socket().read(
201
buf.data(), qMin(static_cast<qint64>(buf.size()), m_dataToRead));
205
m_mi->setLastErrorDescription(
206
QString("failed to read data: %1").arg(
207
m_mi->socket().errorString()));
209
done_(Internal_Failed);
214
m_dataToRead -= retVal;
215
m_dataRead.append(QByteArray(buf.data(), retVal));
222
while(m_dataToRead > 0);
224
if (m_dataToRead <= 0)
226
done_(Internal_FinishedSuccessfully);
230
bool HHttpAsyncOperation::readChunkedSizeLine()
232
if (m_mi->socket().bytesAvailable() <= 0)
238
if (!HHttpUtils::readLines(m_mi->socket(), buf, 1))
240
// No size line. It should be available at this point.
241
m_mi->setLastErrorDescription("missing chunk-size line");
242
done_(Internal_Failed);
246
qint32 endOfSize = buf.indexOf(';');
250
endOfSize = buf.size() - 2; // 2 == crlf
252
QByteArray sizeLine = buf.left(endOfSize);
255
qint32 chunkSize = sizeLine.toInt(&ok, 16);
256
if (!ok || chunkSize < 0)
258
m_mi->setLastErrorDescription(
259
QString("invalid chunk-size line: %1").arg(
260
QString::fromUtf8(sizeLine)));
262
done_(Internal_Failed);
268
// the last chunk, ignore possible trailers
269
done_(Internal_FinishedSuccessfully);
273
m_dataToRead = chunkSize;
274
m_state = Internal_ReadingChunk;
279
bool HHttpAsyncOperation::readChunk()
282
tmp.resize(m_dataToRead);
284
qint32 read = m_mi->socket().read(tmp.data(), tmp.size());
288
m_mi->setLastErrorDescription(QString(
289
"failed to read chunk: %1").arg(m_mi->socket().errorString()));
291
done_(Internal_Failed);
296
// couldn't read the entire chunk in one pass
301
m_dataRead.append(tmp);
303
m_dataToRead -= read;
304
if (m_dataToRead > 0)
306
// couldn't read the entire chunk in one pass
310
// if here, the entire chunk data is read.
311
// clear the remaining crlf and move to the next chunk
314
m_mi->socket().getChar(&c);
315
m_mi->socket().getChar(&c);
317
m_state = Internal_ReadingChunkSizeLine;
322
bool HHttpAsyncOperation::readHeader()
324
if (!HHttpUtils::readLines(m_mi->socket(), m_dataRead, 2))
326
m_mi->setLastErrorDescription(QString(
327
"failed to read HTTP header: %1").arg(m_mi->socket().errorString()));
329
done_(Internal_Failed);
333
if (m_opType == ReceiveRequest)
335
m_headerRead = new HHttpRequestHeader(QString::fromUtf8(m_dataRead));
339
m_headerRead = new HHttpResponseHeader(QString::fromUtf8(m_dataRead));
344
if (!m_headerRead->isValid())
346
m_mi->setLastErrorDescription("read invalid HTTP header");
347
done_(Internal_Failed);
351
m_mi->setKeepAlive(HHttpUtils::keepAlive(*m_headerRead));
353
if (m_headerRead->hasContentLength())
355
m_dataToRead = m_headerRead->contentLength();
356
if (m_dataToRead == 0)
358
done_(Internal_FinishedSuccessfully);
362
else if (m_headerRead->value("TRANSFER-ENCODING") != "chunked")
364
done_(Internal_FinishedSuccessfully);
368
m_state = Internal_ReadingData;
372
bool HHttpAsyncOperation::readData()
374
if (!m_mi->socket().bytesAvailable())
379
bool chunked = m_headerRead->value("TRANSFER-ENCODING") == "chunked";
382
if (m_headerRead->hasContentLength())
384
m_mi->setLastErrorDescription("read invalid HTTP header where both "
385
"TRANSFER-ENCODING and CONTENT-LENGTH where defined");
387
done_(Internal_Failed);
391
m_state = Internal_ReadingChunkSizeLine;
395
if (m_headerRead->hasContentLength())
401
// not chunked and content length is not specified ==>
402
// no way to know what to expect ==> read all that is available
403
QByteArray body = m_mi->socket().readAll();
404
m_dataRead.append(body);
406
done_(Internal_FinishedSuccessfully);
414
bool HHttpAsyncOperation::run()
416
if (m_dataToSend.isEmpty())
418
m_state = Internal_ReadingHeader;
422
if (m_mi->socket().state() != QTcpSocket::ConnectedState)
424
m_mi->setLastErrorDescription("socket is not connected");
428
qint32 indexOfData = m_dataToSend.indexOf("\r\n\r\n");
429
Q_ASSERT(indexOfData > 0);
431
if (m_mi->chunkedInfo().max() > 0 &&
432
m_dataToSend.size() - indexOfData > m_mi->chunkedInfo().max())
434
// send the http header first (it is expected that the header has been
435
// properly setup for chunked transfer, as it should be, since this is
436
// private stuff not influenced by public input)
438
qint32 endOfHdr = m_dataToSend.indexOf("\r\n\r\n") + 4;
439
m_dataSent = m_mi->socket().write(m_dataToSend.data(), endOfHdr);
441
if (m_dataSent != endOfHdr)
443
m_mi->setLastErrorDescription(QString(
444
"failed to send HTTP header %1").arg(
445
m_mi->socket().errorString()));
447
done_(Internal_Failed, false);
451
m_state = Internal_WritingChunkedSizeLine;
456
m_dataSent = m_mi->socket().write(m_dataToSend);
460
m_mi->setLastErrorDescription(
461
QString("failed to send data: %1").arg(
462
m_mi->socket().errorString()));
464
done_(Internal_Failed, false);
468
m_state = Internal_WritingBlob;
470
if (m_mi->sendWait() > 0)
472
if (m_mi->socket().waitForBytesWritten(m_mi->sendWait()))
478
m_mi->setLastErrorDescription(QString(
479
"failed to send data %1").arg(m_mi->socket().errorString()));
480
done_(Internal_Failed, false);
489
void HHttpAsyncOperation::done_(InternalState state, bool emitSignal)
491
m_mi->socket().disconnect(this);
493
Q_ASSERT((state == Internal_FinishedSuccessfully && (headerRead() || m_opType == SendOnly)) ||
494
state != Internal_FinishedSuccessfully);
503
void HHttpAsyncOperation::bytesWritten(qint64)
505
if (m_state == Internal_WritingBlob)
507
if (m_dataSent < m_dataToSend.size())
509
qint64 dataSent = m_mi->socket().write(
510
m_dataToSend.data() + m_dataSent,
511
m_dataToSend.size() - m_dataSent);
515
m_mi->setLastErrorDescription(
516
QString("failed to send data: %1").arg(
517
m_mi->socket().errorString()));
519
done_(Internal_Failed);
523
m_dataSent += dataSent;
526
if (m_dataSent >= m_dataToSend.size())
528
if (m_opType == SendOnly)
530
done_(Internal_FinishedSuccessfully);
534
m_state = Internal_ReadingHeader;
538
else if (m_state == Internal_WritingChunk ||
539
m_state == Internal_WritingChunkedSizeLine)
545
void HHttpAsyncOperation::readyRead()
547
if (m_state == Internal_ReadingHeader)
555
if (m_state == Internal_ReadingData)
563
for(; m_state == Internal_ReadingChunkSizeLine ||
564
m_state == Internal_ReadingChunk;)
566
// the request contained chunked data
568
if (m_state == Internal_ReadingChunkSizeLine)
570
if (!readChunkedSizeLine())
572
// no more data available at the moment
577
if (m_state == Internal_ReadingChunk)
581
// no more data available at the moment
588
void HHttpAsyncOperation::error(QAbstractSocket::SocketError err)
590
if (err != QAbstractSocket::RemoteHostClosedError)
592
done_(Internal_Failed);
595
else if (m_state >= Internal_Failed && m_state < Internal_ReadingHeader)
597
done_(Internal_Failed);
600
else if (m_dataToRead > 0)
602
m_mi->setLastErrorDescription(
603
"remote host closed connection before all data could be read");
605
done_(Internal_Failed);
608
else if (m_state == Internal_ReadingHeader)
610
if (m_dataRead.size() <= 0)
612
m_mi->setLastErrorDescription(
613
QString("failed to read HTTP header: %1").arg(m_mi->socket().errorString()));
614
done_(Internal_Failed);
618
if (m_opType == ReceiveRequest)
620
m_headerRead = new HHttpRequestHeader(QString::fromUtf8(m_dataRead));
624
m_headerRead = new HHttpResponseHeader(QString::fromUtf8(m_dataRead));
627
if (!m_headerRead->isValid())
629
m_mi->setLastErrorDescription("read invalid HTTP header");
630
done_(Internal_Failed);
635
// at this point a header is successfully read and possibly some data ==>
636
// it is up to the user to check the contents of the data to determine was the
637
// operation "really" successful
638
done_(Internal_FinishedSuccessfully);
641
HHttpAsyncOperation::State HHttpAsyncOperation::state() const
645
case Internal_Failed:
648
case Internal_NotStarted:
651
case Internal_WritingBlob:
652
case Internal_WritingChunkedSizeLine:
653
case Internal_WritingChunk:
656
case Internal_ReadingHeader:
657
case Internal_ReadingData:
658
case Internal_ReadingChunkSizeLine:
659
case Internal_ReadingChunk:
662
case Internal_FinishedSuccessfully:
671
/*******************************************************************************
673
******************************************************************************/
674
HHttpAsyncHandler::HHttpAsyncHandler(
675
const QByteArray& loggingIdentifier, QObject* parent) :
677
m_loggingIdentifier(loggingIdentifier), m_operations(),
682
HHttpAsyncHandler::~HHttpAsyncHandler()
686
void HHttpAsyncHandler::done(unsigned int id)
688
HHttpAsyncOperation* ao = m_operations.value(id);
691
Q_ASSERT(ao->state() != HHttpAsyncOperation::NotStarted);
693
bool ok = ao->disconnect(this);
694
Q_ASSERT(ok); Q_UNUSED(ok)
696
m_operations.remove(id);
698
emit msgIoComplete(ao);
701
HHttpAsyncOperation* HHttpAsyncHandler::msgIo(
702
HMessagingInfo* mi, const QByteArray& req)
705
Q_ASSERT(!req.isEmpty());
707
HHttpAsyncOperation* ao =
708
new HHttpAsyncOperation(
709
m_loggingIdentifier, ++m_lastIdUsed, mi, req, false, this);
711
bool ok = connect(ao, SIGNAL(done(unsigned int)), this, SLOT(done(unsigned int)));
713
Q_ASSERT(ok); Q_UNUSED(ok)
715
m_operations.insert(ao->id(), ao);
719
m_operations.remove(ao->id());
727
HHttpAsyncOperation* HHttpAsyncHandler::msgIo(
728
HMessagingInfo* mi, HHttpRequestHeader& reqHdr, const QtSoapMessage& soapMsg)
730
QByteArray dataToSend =
731
HHttpMessageCreator::setupData(
732
reqHdr, soapMsg.toXmlString().toUtf8(), *mi, TextXml);
734
return msgIo(mi, dataToSend);
737
HHttpAsyncOperation* HHttpAsyncHandler::send(
738
HMessagingInfo* mi, const QByteArray& data)
741
Q_ASSERT(!data.isEmpty());
743
HHttpAsyncOperation* ao =
744
new HHttpAsyncOperation(
745
m_loggingIdentifier, ++m_lastIdUsed, mi, data, true, this);
747
bool ok = connect(ao, SIGNAL(done(unsigned int)), this, SLOT(done(unsigned int)));
748
Q_ASSERT(ok); Q_UNUSED(ok)
750
m_operations.insert(ao->id(), ao);
754
m_operations.remove(ao->id());
762
HHttpAsyncOperation* HHttpAsyncHandler::receive(
763
HMessagingInfo* mi, bool waitingRequest)
767
HHttpAsyncOperation* ao =
768
new HHttpAsyncOperation(
769
m_loggingIdentifier, ++m_lastIdUsed, mi, waitingRequest, this);
771
bool ok = connect(ao, SIGNAL(done(unsigned int)), this, SLOT(done(unsigned int)));
772
Q_ASSERT(ok); Q_UNUSED(ok)
774
m_operations.insert(ao->id(), ao);
778
m_operations.remove(ao->id());