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