1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qnetworkaccessdebugpipebackend_p.h" |
5 | #include "QtCore/qdatastream.h" |
6 | #include <QCoreApplication> |
7 | #include <QStringList> |
8 | #include <QUrlQuery> |
9 | #include "private/qnoncontiguousbytedevice_p.h" |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | using namespace Qt::StringLiterals; |
14 | |
15 | #ifdef QT_BUILD_INTERNAL |
16 | |
17 | enum { |
18 | ReadBufferSize = 16384, |
19 | WriteBufferSize = ReadBufferSize |
20 | }; |
21 | |
22 | QStringList QNetworkAccessDebugPipeBackendFactory::supportedSchemes() const |
23 | { |
24 | return QStringList(QStringLiteral("debugpipe" )); |
25 | } |
26 | |
27 | QNetworkAccessBackend * |
28 | QNetworkAccessDebugPipeBackendFactory::create(QNetworkAccessManager::Operation op, |
29 | const QNetworkRequest &request) const |
30 | { |
31 | // is it an operation we know of? |
32 | switch (op) { |
33 | case QNetworkAccessManager::GetOperation: |
34 | case QNetworkAccessManager::PutOperation: |
35 | break; |
36 | |
37 | default: |
38 | // no, we can't handle this operation |
39 | return nullptr; |
40 | } |
41 | |
42 | QUrl url = request.url(); |
43 | if (url.scheme() == "debugpipe"_L1 ) |
44 | return new QNetworkAccessDebugPipeBackend; |
45 | return nullptr; |
46 | } |
47 | |
48 | QNetworkAccessDebugPipeBackend::QNetworkAccessDebugPipeBackend() |
49 | : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Networked), |
50 | bareProtocol(false), |
51 | hasUploadFinished(false), |
52 | hasDownloadFinished(false), |
53 | hasEverythingFinished(false), |
54 | bytesDownloaded(0), |
55 | bytesUploaded(0) |
56 | { |
57 | } |
58 | |
59 | QNetworkAccessDebugPipeBackend::~QNetworkAccessDebugPipeBackend() |
60 | { |
61 | // this is signals disconnect, not network! |
62 | socket.disconnect(receiver: this); // we're not interested in the signals at this point |
63 | } |
64 | |
65 | void QNetworkAccessDebugPipeBackend::open() |
66 | { |
67 | socket.connectToHost(hostName: url().host(), port: url().port(defaultPort: 12345)); |
68 | socket.setReadBufferSize(ReadBufferSize); |
69 | |
70 | // socket ready read -> we can push from socket to downstream |
71 | connect(asender: &socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); |
72 | connect(asender: &socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError())); |
73 | connect(asender: &socket, SIGNAL(disconnected()), SLOT(socketDisconnected())); |
74 | connect(asender: &socket, SIGNAL(connected()), SLOT(socketConnected())); |
75 | // socket bytes written -> we can push more from upstream to socket |
76 | connect(asender: &socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); |
77 | |
78 | bareProtocol = QUrlQuery(url()).queryItemValue(key: "bare"_L1 ) == "1"_L1 ; |
79 | |
80 | if (operation() == QNetworkAccessManager::PutOperation) { |
81 | createUploadByteDevice(); |
82 | QObject::connect(sender: uploadByteDevice(), SIGNAL(readyRead()), receiver: this, |
83 | SLOT(uploadReadyReadSlot())); |
84 | QMetaObject::invokeMethod(obj: this, member: "uploadReadyReadSlot" , c: Qt::QueuedConnection); |
85 | } |
86 | } |
87 | |
88 | void QNetworkAccessDebugPipeBackend::socketReadyRead() |
89 | { |
90 | readyRead(); |
91 | } |
92 | |
93 | qint64 QNetworkAccessDebugPipeBackend::read(char *data, qint64 maxlen) |
94 | { |
95 | qint64 haveRead = socket.read(data, maxlen); |
96 | |
97 | if (haveRead == -1) { |
98 | hasDownloadFinished = true; |
99 | // this ensures a good last downloadProgress is emitted |
100 | auto h = headers(); |
101 | h.removeAll(name: QHttpHeaders::WellKnownHeader::ContentLength); |
102 | setHeaders(std::move(h)); |
103 | possiblyFinish(); |
104 | return haveRead; |
105 | } |
106 | |
107 | bytesDownloaded += haveRead; |
108 | return haveRead; |
109 | } |
110 | |
111 | qint64 QNetworkAccessDebugPipeBackend::bytesAvailable() const |
112 | { |
113 | return socket.bytesAvailable(); |
114 | } |
115 | |
116 | void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) |
117 | { |
118 | pushFromUpstreamToSocket(); |
119 | } |
120 | |
121 | void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot() |
122 | { |
123 | pushFromUpstreamToSocket(); |
124 | } |
125 | |
126 | void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() |
127 | { |
128 | // FIXME |
129 | if (operation() == QNetworkAccessManager::PutOperation) { |
130 | if (hasUploadFinished) |
131 | return; |
132 | |
133 | forever { |
134 | if (socket.bytesToWrite() >= WriteBufferSize) |
135 | return; |
136 | |
137 | QByteArray data(WriteBufferSize, Qt::Uninitialized); |
138 | qint64 haveRead = uploadByteDevice()->peek(data: data.data(), maxlen: data.size()); |
139 | if (haveRead == -1) { |
140 | // EOF |
141 | hasUploadFinished = true; |
142 | possiblyFinish(); |
143 | break; |
144 | } else if (haveRead == 0) { |
145 | // nothing to read right now, we will be called again later |
146 | break; |
147 | } else { |
148 | qint64 haveWritten; |
149 | data.truncate(pos: haveRead); |
150 | haveWritten = socket.write(data: std::move(data)); |
151 | |
152 | if (haveWritten < 0) { |
153 | // write error! |
154 | QString msg = QCoreApplication::translate(context: "QNetworkAccessDebugPipeBackend" , key: "Write error writing to %1: %2" ) |
155 | .arg(args: url().toString(), args: socket.errorString()); |
156 | error(code: QNetworkReply::ProtocolFailure, errorString: msg); |
157 | finished(); |
158 | return; |
159 | } else { |
160 | uploadByteDevice()->skip(maxSize: haveWritten); |
161 | bytesUploaded += haveWritten; |
162 | } |
163 | |
164 | //QCoreApplication::processEvents(); |
165 | } |
166 | } |
167 | } |
168 | } |
169 | |
170 | void QNetworkAccessDebugPipeBackend::possiblyFinish() |
171 | { |
172 | if (hasEverythingFinished) |
173 | return; |
174 | hasEverythingFinished = true; |
175 | |
176 | if ((operation() == QNetworkAccessManager::GetOperation) && hasDownloadFinished) { |
177 | socket.close(); |
178 | finished(); |
179 | } else if ((operation() == QNetworkAccessManager::PutOperation) && hasUploadFinished) { |
180 | socket.close(); |
181 | finished(); |
182 | } |
183 | |
184 | |
185 | } |
186 | |
187 | void QNetworkAccessDebugPipeBackend::close() |
188 | { |
189 | qWarning(msg: "QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d" ,operation()); |
190 | //if (operation() == QNetworkAccessManager::GetOperation) |
191 | // socket.disconnectFromHost(); |
192 | } |
193 | |
194 | |
195 | void QNetworkAccessDebugPipeBackend::socketError() |
196 | { |
197 | qWarning(msg: "QNetworkAccessDebugPipeBackend::socketError() %d" ,socket.error()); |
198 | QNetworkReply::NetworkError code; |
199 | switch (socket.error()) { |
200 | case QAbstractSocket::RemoteHostClosedError: |
201 | return; // socketDisconnected will be called |
202 | |
203 | case QAbstractSocket::NetworkError: |
204 | code = QNetworkReply::UnknownNetworkError; |
205 | break; |
206 | |
207 | default: |
208 | code = QNetworkReply::ProtocolFailure; |
209 | break; |
210 | } |
211 | |
212 | error(code, errorString: QNetworkAccessDebugPipeBackend::tr(s: "Socket error on %1: %2" ) |
213 | .arg(args: url().toString(), args: socket.errorString())); |
214 | finished(); |
215 | disconnect(sender: &socket, SIGNAL(disconnected()), receiver: this, SLOT(socketDisconnected())); |
216 | |
217 | } |
218 | |
219 | void QNetworkAccessDebugPipeBackend::socketDisconnected() |
220 | { |
221 | if (socket.bytesToWrite() == 0) { |
222 | // normal close |
223 | } else { |
224 | readyRead(); // @todo this is odd |
225 | // abnormal close |
226 | QString msg = QNetworkAccessDebugPipeBackend::tr(s: "Remote host closed the connection prematurely on %1" ) |
227 | .arg(a: url().toString()); |
228 | error(code: QNetworkReply::RemoteHostClosedError, errorString: msg); |
229 | finished(); |
230 | } |
231 | } |
232 | |
233 | void QNetworkAccessDebugPipeBackend::socketConnected() |
234 | { |
235 | } |
236 | |
237 | |
238 | #endif |
239 | |
240 | QT_END_NAMESPACE |
241 | |
242 | #include "moc_qnetworkaccessdebugpipebackend_p.cpp" |
243 | |