1 /*
2 * Copyright (C) 2011 by Tommi Meakitalo
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * As a special exception, you may use this file as part of a free
10 * software library without restriction. Specifically, if other files
11 * instantiate templates or use macros or inline functions from this
12 * file, or you compile this file and link it with other files to
13 * produce an executable, this file does not by itself cause the
14 * resulting executable to be covered by the GNU General Public
15 * License. This exception does not however invalidate any other
16 * reasons why the executable file might be covered by the GNU Library
17 * General Public License.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 */
28
29 #include "httpclientimpl.h"
30 #include "cxxtools/remoteprocedure.h"
31 #include "cxxtools/textstream.h"
32 #include "cxxtools/jsonformatter.h"
33 #include "cxxtools/http/replyheader.h"
34 #include "cxxtools/selectable.h"
35 #include "cxxtools/utf8codec.h"
36 #include "cxxtools/ioerror.h"
37 #include "cxxtools/clock.h"
38 #include "cxxtools/log.h"
39
40 #include <stdexcept>
41 #include <string.h>
42
43 log_define("cxxtools.json.client.impl")
44
45 namespace cxxtools
46 {
47
48 namespace json
49 {
50
HttpClientImpl()51 HttpClientImpl::HttpClientImpl()
52 : _timeout(Selectable::WaitInfinite),
53 _proc(0),
54 _exceptionPending(false),
55 _count(0)
56 {
57 _request.method("POST");
58 cxxtools::connect(_client.headerReceived, *this, &HttpClientImpl::onReplyHeader);
59 cxxtools::connect(_client.bodyAvailable, *this, &HttpClientImpl::onReplyBody);
60 cxxtools::connect(_client.replyFinished, *this, &HttpClientImpl::onReplyFinished);
61 }
62
beginCall(IComposer & r,IRemoteProcedure & method,IDecomposer ** argv,unsigned argc)63 void HttpClientImpl::beginCall(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
64 {
65 if (_client.selector() == 0)
66 throw std::logic_error("cannot run async rpc request without a selector");
67
68 if (_proc)
69 throw std::logic_error("asyncronous request already running");
70
71 _proc = &method;
72
73 prepareRequest(method.name(), argv, argc);
74
75 _client.beginExecute(_request);
76
77 _scanner.begin(_deserializer, r);
78 }
79
80
endCall()81 void HttpClientImpl::endCall()
82 {
83 log_debug("end call; errorPending=" << _exceptionPending);
84 if (_exceptionPending)
85 {
86 _exceptionPending = false;
87 throw;
88 }
89
90 _client.endExecute();
91 }
92
93
call(IComposer & r,IRemoteProcedure & method,IDecomposer ** argv,unsigned argc)94 void HttpClientImpl::call(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
95 {
96 _proc = &method;
97
98 prepareRequest(method.name(), argv, argc);
99
100 _client.execute(_request);
101
102 _scanner.begin(_deserializer, r);
103
104 char ch;
105 std::istream& is = _client.in();
106 while (is.get(ch))
107 {
108 if (_scanner.advance(ch))
109 {
110 log_debug("scanner finished");
111 _proc = 0;
112 _scanner.finalizeReply();
113 return;
114 }
115 }
116
117 throw std::runtime_error("unexpected end of data");
118 }
119
120
activeProcedure() const121 const IRemoteProcedure* HttpClientImpl::activeProcedure() const
122 {
123 return _proc;
124 }
125
cancel()126 void HttpClientImpl::cancel()
127 {
128 _client.cancel();
129 _proc = 0;
130 }
131
132 // private members
133
prepareRequest(const String & name,IDecomposer ** argv,unsigned argc)134 void HttpClientImpl::prepareRequest(const String& name, IDecomposer** argv, unsigned argc)
135 {
136 _request.clear();
137 _request.setHeader("Content-Type", "application/json");
138 _request.method("POST");
139
140 TextOStream ts(_request.body(), new Utf8Codec());
141 JsonFormatter formatter;
142
143 formatter.begin(ts);
144
145 formatter.beginObject(std::string(), std::string());
146
147 formatter.addValueStdString("jsonrpc", std::string(), "2.0");
148 formatter.addValueString("method", std::string(), name);
149 formatter.addValueInt("id", "int", ++_count);
150
151 formatter.beginArray("params", std::string());
152
153 for(unsigned n = 0; n < argc; ++n)
154 {
155 argv[n]->format(formatter);
156 }
157
158 formatter.finishArray();
159
160 formatter.finishObject();
161
162 formatter.finish();
163
164 ts.flush();
165 }
166
onReplyHeader(http::Client & client)167 void HttpClientImpl::onReplyHeader(http::Client& client)
168 {
169 verifyHeader(client.header());
170 }
171
onReplyBody(http::Client & client)172 std::size_t HttpClientImpl::onReplyBody(http::Client& client)
173 {
174 std::size_t count = 0;
175 char ch;
176 std::istream& is = client.in();
177 while (is.rdbuf()->in_avail() && is.get(ch))
178 {
179 ++count;
180 if (_scanner.advance(ch))
181 {
182 log_debug("scanner finished");
183 try
184 {
185 _scanner.finalizeReply();
186 }
187 catch (const RemoteException& e)
188 {
189 _proc->setFault(e.rc(), e.text());
190 }
191 catch (const std::exception&)
192 {
193 log_warn("exception occured in finalizeReply");
194 _exceptionPending = true;
195 _proc->onFinished();
196 }
197
198 break;
199 }
200 }
201
202 log_debug("no more data - " << count << " bytes consumed");
203
204 return count;
205 }
206
onReplyFinished(http::Client & client)207 void HttpClientImpl::onReplyFinished(http::Client& client)
208 {
209 log_debug("onReplyFinished; method=" << static_cast<void*>(_proc));
210
211 try
212 {
213 _exceptionPending = false;
214 endCall();
215 }
216 catch (const std::exception& e)
217 {
218 if (!_proc)
219 throw;
220
221 _exceptionPending = true;
222
223 IRemoteProcedure* proc = _proc;
224 _proc = 0;
225 proc->onFinished();
226 if (_exceptionPending)
227 {
228 _exceptionPending = false;
229 throw;
230 }
231 return;
232 }
233
234 IRemoteProcedure* proc = _proc;
235 _proc = 0;
236 proc->onFinished();
237 }
238
wait(std::size_t msecs)239 void HttpClientImpl::wait(std::size_t msecs)
240 {
241 if (!_client.selector())
242 throw std::logic_error("cannot run async rpc request without a selector");
243
244 Clock clock;
245 if (msecs != RemoteClient::WaitInfinite)
246 clock.start();
247
248 std::size_t remaining = msecs;
249
250 while (activeProcedure() != 0)
251 {
252 if (_client.selector()->wait(remaining) == false)
253 throw IOTimeout();
254
255 if (msecs != RemoteClient::WaitInfinite)
256 {
257 std::size_t diff = static_cast<std::size_t>(clock.stop().totalMSecs());
258 remaining = diff >= msecs ? 0 : msecs - diff;
259 }
260 }
261 }
262
verifyHeader(const http::ReplyHeader & header)263 void HttpClientImpl::verifyHeader(const http::ReplyHeader& header)
264 {
265 if (header.httpReturnCode() != 200)
266 {
267 std::ostringstream msg;
268 msg << "invalid http return code "
269 << header.httpReturnCode()
270 << ": "
271 << header.httpReturnText();
272 throw std::runtime_error(msg.str());
273 }
274
275 const char* contentType = header.getHeader("Content-Type");
276 if (contentType == 0)
277 throw std::runtime_error("missing content type header");
278
279 if (::strncasecmp(contentType, "application/json", 16) != 0)
280 {
281 std::ostringstream msg;
282 msg << "invalid content type " << contentType;
283 throw std::runtime_error(msg.str());
284 }
285
286 }
287
288 }
289
290 }
291