1 /*----------------------------------------------------------------------------
2 Name: CallbackServerUnparsed.c
3 Project: xmlBlaster.org
4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
5 Comment: Establish a listen socket for xmlBlaster callbacks
6 Author: "Marcel Ruff" <xmlBlaster@marcelruff.info>
7 Compile:
8 LINUX: gcc -g -Wall -DUSE_MAIN_CB -I.. -o CallbackServerUnparsed CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c
9 WIN: cl /MT -DUSE_MAIN_CB -D_WINDOWS -I.. CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c ws2_32.lib
10 Solaris: cc -g -DUSE_MAIN_CB -I.. -o CallbackServerUnparsed CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c -lsocket -lnsl
11 -----------------------------------------------------------------------------*/
12 #include <stdio.h>
13 #include <string.h>
14 #if defined(WINCE)
15 # if defined(XB_USE_PTHREADS)
16 # include <pthreads/pthread.h>
17 # else
18 /*#include <pthreads/need_errno.h> */
19 static int errno=0; /* single threaded workaround*/
20 # endif
21 #else
22 # include <errno.h>
23 #endif
24 #include <socket/xmlBlasterSocket.h> /* gethostname() */
25 #include <CallbackServerUnparsed.h>
26 #ifdef __IPhoneOS__
27 #include <CoreFoundation/CFSocket.h>
28 #include <XmlBlasterConnectionUnparsed.h>
29 #endif
30 static bool waitOnCallbackThreadAlive(CallbackServerUnparsed *cb, long millis);
31 static bool waitOnCallbackThreadTermination(CallbackServerUnparsed *cb, long millis);
32 static bool useThisSocket(CallbackServerUnparsed *cb, int socketToUse, int socketToUseUdp);
33 static int runCallbackServer(CallbackServerUnparsed *cb);
34 static bool createCallbackServer(CallbackServerUnparsed *cb);
35 static bool isListening(CallbackServerUnparsed *cb);
36 static bool readMessage(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception, bool udp);
37 static ssize_t writenPlain(void *cb, const int fd, const char *ptr, const size_t nbytes);
38 static ssize_t readnPlain(void *cb, const int fd, char *ptr, const size_t nbytes, XmlBlasterNumReadFunc fpNumRead, void *userP2);
39 static bool addResponseListener(CallbackServerUnparsed *cb, MsgRequestInfo *msgRequestInfoP, ResponseFp responseEventFp);
40 static ResponseListener *removeResponseListener(CallbackServerUnparsed *cb, const char *requestId);
41 static void voidSendResponse(CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArr);
42 static void sendResponse(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArr);
43 static void voidSendXmlBlasterException(CallbackServerUnparsed *cb, void *socketDataHolder, XmlBlasterException *exception);
44 static void sendXmlBlasterException(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception);
45 static void voidSendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception);
46 static void sendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception);
47 static void shutdownCallbackServer(CallbackServerUnparsed *cb);
48 static void closeAcceptSocket(CallbackServerUnparsed *cb);
49
50 /*
51 static void xmlBlasterNumRead_test(void *xb, const size_t currBytesRead, const size_t nbytes) {
52 printf("xmlBlasterSocket.c: DEUBG ONLY currBytesRead=%ld nbytes=%ld\n", (long)currBytesRead, (long)nbytes);
53 }
54 */
55
56
57 /**
58 * See header for a description.
59 */
60 CallbackServerUnparsed *getCallbackServerUnparsed(int argc, const char* const* argv,
61 UpdateCbFp updateCb, void *updateCbUserData)
62 {
63 CallbackServerUnparsed *cb = (CallbackServerUnparsed *)calloc(1,
64 sizeof(CallbackServerUnparsed));
65 if (cb == 0) return cb;
66 cb->props = createProperties(argc, argv);
67 if (cb->props == 0) {
68 freeCallbackServerUnparsed(&cb);
69 return (CallbackServerUnparsed *)0;
70 }
71 cb->stopListenLoop = false;
72 cb->listenSocket = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
73 cb->acceptSocket = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
74 cb->socketUdp = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
75 cb->useThisSocket = useThisSocket;
76 cb->runCallbackServer = runCallbackServer;
77 cb->isListening = isListening;
78 cb->shutdown = shutdownCallbackServer;
79 cb->reusingConnectionSocket = false; /* is true if we tunnel callback through the client connection socket */
80 cb->logLevel = parseLogLevel(cb->props->getString(cb->props, "logLevel", "WARN"));
81 cb->log = xmlBlasterDefaultLogging;
82 cb->logUserP = 0;
83 cb->waitOnCallbackThreadAlive = waitOnCallbackThreadAlive;
84 cb->waitOnCallbackThreadTermination = waitOnCallbackThreadTermination;
85 cb->hostCB = strcpyAlloc(cb->props->getString(cb->props, "dispatch/callback/plugin/socket/hostname", 0));
86 cb->portCB = cb->props->getInt(cb->props, "dispatch/callback/plugin/socket/port", DEFAULT_CALLBACK_SERVER_PORT);
87 cb->updateCb = updateCb;
88 cb->updateCbUserData = updateCbUserData; /* A optional pointer from the client code which is returned to the update() function call */
89 memset(cb->responseListener, 0, MAX_RESPONSE_LISTENER_SIZE*sizeof(ResponseListener));
90 pthread_mutex_init(&cb->responseListenerMutex, NULL); /* returns always 0 */
91 cb->addResponseListener = addResponseListener;
92 cb->removeResponseListener = removeResponseListener;
93 cb->_threadIsAliveOnce = true;
94 cb->threadIsAlive = false;
95 cb->sendResponse = voidSendResponse;
96 cb->sendXmlBlasterException = voidSendXmlBlasterException;
97 cb->sendResponseOrException = voidSendResponseOrException;
98
99 cb->writeToSocket.writeToSocketFuncP = writenPlain;
100 cb->writeToSocket.userP = cb;
101
102 cb->readFromSocket.readFromSocketFuncP = readnPlain;
103 cb->readFromSocket.userP = cb;
104 cb->readFromSocket.numReadFuncP = 0; /* xmlBlasterNumRead_test */
105 cb->readFromSocket.numReadUserP = 0;
106 return cb;
107 }
108
109 /*
110 * @see header
111 */
112 bool useThisSocket(CallbackServerUnparsed *cb, int socketToUse, int socketToUseUdp)
113 {
114 #ifdef __IPhoneOS__
115 # pragma unused(fd) /*if (socketToUse < 200) printf("CallbackServerUparsed.c: dummy printf to avoid compiler warning\n");*/
116 cb->portCB = 12345;
117 strcpyRealloc(&cb->hostCB, "127.0.0.1"); /* inet_ntoa holds the host in an internal static string */
118 /*
119 cb->listenSocket = CFSocketGetNative(globalIPhoneXb->cfSocketRef);
120
121 cb->acceptSocket = CFSocketGetNative(globalIPhoneXb->cfSocketRef);
122 */
123 cb->listenSocket = 0;
124
125 cb->acceptSocket = 0;
126
127 cb->socketUdp = socketToUseUdp;
128 cb->reusingConnectionSocket = true; /* we tunnel callback through the client connection socket */
129 #else
130 struct sockaddr_in localAddr;
131 socklen_t size = (socklen_t)sizeof(localAddr);
132 memset((char *)&localAddr, 0, (size_t)size);
133 if (getsockname(socketToUse, (struct sockaddr *)&localAddr, &size) == -1) {
134 if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
135 "Can't determine the local socket host and port, errno=%d", errno);
136 return false;
137 }
138 cb->portCB = (int)ntohs(localAddr.sin_port);
139 strcpyRealloc(&cb->hostCB, inet_ntoa(localAddr.sin_addr)); /* inet_ntoa holds the host in an internal static string */
140
141 cb->listenSocket = socketToUse;
142 cb->acceptSocket = socketToUse;
143 cb->socketUdp = socketToUseUdp;
144 cb->reusingConnectionSocket = true; /* we tunnel callback through the client connection socket */
145
146 if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
147 "Forced callback server to reuse socket descriptor '%d' on localHostname=%s localPort=%d",
148 socketToUse, cb->hostCB, cb->portCB);
149 #endif
150 return true;
151 }
152
153 /**
154 * Wait after pthread_create() until thread is running.
155 * @return false if not alive after given millis
156 */
157 static bool waitOnCallbackThreadAlive(CallbackServerUnparsed *cb, long millis) {
158 int i;
159 const int count = 100;
160 const int milliStep = (millis < count) ? 1 : (int)(millis / count);
161 for(i=0; i<count; i++) {
162 if (cb->_threadIsAliveOnce) {
163 return true;
164 }
165 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
166 "waitOnCallbackThreadAlive(i=%d/%d) waiting %d millis ...", i, count, milliStep);
167 sleepMillis(milliStep);
168 }
169 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
170 "waitOnCallbackThreadAlive() failed after %d milliseconds, thread has never reached alive", millis);
171 return false;
172 }
173
174 /**
175 * For pthread_detached operation only (does not make sense in pthread_join() mode).
176 * @return false if not terminated after given millis
177 */
178 static bool waitOnCallbackThreadTermination(CallbackServerUnparsed *cb, long millis) {
179 int i;
180 const int count = 100;
181 const int milliStep = (millis < count) ? 1 : (int)(millis / count);
182 for(i=0; i<count; i++) {
183 if (!cb->threadIsAlive) {
184 return true;
185 }
186 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
187 "waitOnCallbackThreadTermination(i=%d/%d) waiting %d millis ...", i, count, milliStep);
188 sleepMillis(milliStep);
189 }
190 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
191 "waitOnCallbackThreadTermination() failed after %d milliseconds, thread has not terminated, it seems to block on the socket", millis);
192 return false;
193 }
194
195 void freeCallbackServerUnparsed(CallbackServerUnparsed **cb_)
196 {
197 CallbackServerUnparsed *cb = *cb_;
198 if (cb != 0) {
199 bool hasTerminated;
200 shutdownCallbackServer(cb);
201 hasTerminated = cb->waitOnCallbackThreadTermination(cb, 5000);
202 freeProperties(cb->props);
203 if (hasTerminated) {
204 pthread_mutex_destroy(&cb->responseListenerMutex);
205 free(cb); /* Prefer to have a leak instead of a crash */
206 }
207 *cb_ = 0;
208 }
209 }
210
211 /**
212 * Write uncompressed to socket (not thread safe)
213 */
214 static ssize_t writenPlain(void *userP, const int fd, const char *ptr, const size_t nbytes) {
215 if (userP) userP = 0; /* To avoid compiler warning */
216 return writen(fd, ptr, nbytes);
217 }
218
219 /**
220 * Read data from socket, uncompress data if needed (not thread safe)
221 */
222 static ssize_t readnPlain(void * userP, const int fd, char *ptr, const size_t nbytes, XmlBlasterNumReadFunc fpNumRead, void *userP2) {
223 if (userP) userP = 0; /* To avoid compiler warning */
224 return readn(fd, ptr, nbytes, fpNumRead, userP2);
225 }
226
227 static int responseListenerMutexLock(CallbackServerUnparsed *cb) {
228 int retInt = 0;
229 if ((retInt = pthread_mutex_lock(&cb->responseListenerMutex)) != 0) {
230 char p[XMLBLASTEREXCEPTION_MESSAGE_LEN];
231 SNPRINTF(p, XMLBLASTEREXCEPTION_MESSAGE_LEN,
232 "[%.100s:%d] Error trying to lock cbMutex %d", __FILE__, __LINE__, retInt);
233 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, p);
234 }
235 return retInt;
236 }
237
238 #if defined(_WINDOWS)
239 static char *strerror_r(int retInt, char * errnoStr, size_t size) {
240 /*int ret = */strerror_s(errnoStr, size, retInt);
241 return errnoStr;
242 }
243 #endif
244
245 static int responseListenerMutexUnLock(CallbackServerUnparsed *cb) {
246 int retInt = 0;
247 if ((retInt = pthread_mutex_unlock(&cb->responseListenerMutex)) != 0) {
248 char p[XMLBLASTEREXCEPTION_MESSAGE_LEN];
249 char errnoStr[XMLBLASTEREXCEPTION_MESSAGE_LEN];
250 strerror_r(retInt, errnoStr, XMLBLASTEREXCEPTION_MESSAGE_LEN);
251 SNPRINTF(p, XMLBLASTEREXCEPTION_MESSAGE_LEN,
252 "[%.100s:%d] Error trying to unlock cbMutex %d = %s", __FILE__, __LINE__, retInt, errnoStr);
253 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, p);
254 }
255 return retInt;
256 }
257
258 static bool addResponseListener(CallbackServerUnparsed *cb, MsgRequestInfo *msgRequestInfoP, ResponseFp responseEventFp) {
259 int i;
260 if (responseEventFp == 0) {
261 return false;
262 }
263 responseListenerMutexLock(cb);
264 for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
265 if (cb->responseListener[i].msgRequestInfoP == 0) {
266 cb->responseListener[i].msgRequestInfoP = msgRequestInfoP;
267 cb->responseListener[i].responseEventFp = responseEventFp;
268 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
269 "addResponseListener(i=%d, requestId=%s)", i, msgRequestInfoP->requestIdStr);
270 responseListenerMutexUnLock(cb);
271 return true;
272 }
273 }
274 responseListenerMutexUnLock(cb);
275 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
276 "PANIC too many requests (%d) are waiting for a response, you are not registered", MAX_RESPONSE_LISTENER_SIZE);
277 return false;
278 }
279
280 static ResponseListener *getResponseListener(CallbackServerUnparsed *cb, const char *requestId) {
281 int i;
282 if (requestId == 0) {
283 return 0;
284 }
285 responseListenerMutexLock(cb);
286 for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
287 const MsgRequestInfo * const pp = cb->responseListener[i].msgRequestInfoP;
288 if (pp == 0) {
289 continue;
290 }
291 if (!strcmp(pp->requestIdStr, requestId)) {
292 responseListenerMutexUnLock(cb);
293 return &cb->responseListener[i];
294 }
295 }
296 responseListenerMutexUnLock(cb);
297 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "RequestId '%s' is not registered", requestId);
298 return 0;
299 }
300
301 static ResponseListener *removeResponseListener(CallbackServerUnparsed *cb, const char *requestId) {
302 int i;
303 responseListenerMutexLock(cb);
304 for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
305 const MsgRequestInfo * const pp = cb->responseListener[i].msgRequestInfoP;
306 if (pp == 0) {
307 continue;
308 }
309 if (!strcmp(pp->requestIdStr, requestId)) {
310 cb->responseListener[i].msgRequestInfoP = 0;
311 responseListenerMutexUnLock(cb);
312 return &cb->responseListener[i];
313 }
314 }
315 responseListenerMutexUnLock(cb);
316 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "Can't remove requestId '%s', requestId is not registered", requestId);
317 return (ResponseListener *)0;
318 }
319
320 /**
321 * Called by listenLoop when a new message has arrived.
322 */
323 static void handleMessage(CallbackServerUnparsed *cb, SocketDataHolder* socketDataHolder, XmlBlasterException* xmlBlasterException, bool success) {
324
325 MsgUnitArr *msgUnitArrP;
326
327 if (success == false) { /* EOF */
328 int i;
329 if (!cb->reusingConnectionSocket)
330 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__, "Lost callback socket connection to xmlBlaster (EOF)");
331 closeAcceptSocket(cb);
332 /* Notify pending requests, otherwise they block in their mutex for a minute ... */
333 for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
334 XmlBlasterException exception;
335 ResponseListener *listener;
336 MsgRequestInfo *msgRequestInfoP;
337
338 responseListenerMutexLock(cb);
339 listener = &cb->responseListener[i];
340 if (listener->msgRequestInfoP == 0) {
341 responseListenerMutexUnLock(cb);
342 continue;
343 }
344 /* Handle waiting MSG_TYPE_INVOKE threads (oneways are not in this list) */
345 msgRequestInfoP = listener->msgRequestInfoP;
346 cb->responseListener[i].msgRequestInfoP = 0;
347 responseListenerMutexUnLock(cb);
348
349 initializeXmlBlasterException(&exception);
350
351 /* Simulate an exception on client side ... */
352 socketDataHolder->type = (char)MSG_TYPE_EXCEPTION;
353 strncpy0(socketDataHolder->requestId, msgRequestInfoP->requestIdStr, MAX_REQUESTID_LEN);
354 strncpy0(socketDataHolder->methodName, msgRequestInfoP->methodName, MAX_METHODNAME_LEN);
355
356 exception.remote = true;
357 strncpy0(exception.errorCode, "communication.noConnection", XMLBLASTEREXCEPTION_ERRORCODE_LEN);
358 SNPRINTF(exception.message, XMLBLASTEREXCEPTION_MESSAGE_LEN,
359 "[%.100s:%d] Lost connection to xmlBlaster with server side EOF", __FILE__, __LINE__);
360
361 encodeXmlBlasterException(&socketDataHolder->blob, &exception, false);
362
363 /* Takes a clone of socketDataHolder->blob */
364 listener->responseEventFp(msgRequestInfoP, socketDataHolder);
365 /* Now the client thread has wakened up and returns:
366 * msgRequestInfoP is invalid now as it was on client thread stack
367 */
368
369 freeBlobHolderContent(&socketDataHolder->blob);
370 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
371 "Notified pending requestId '%s' about lost socket connection", socketDataHolder->requestId);
372 }
373 return;
374 }
375
376 if (*xmlBlasterException->errorCode != 0) {
377 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
378 "Couldn't read message from xmlBlaster: errorCode=%s message=%s",
379 xmlBlasterException->errorCode, xmlBlasterException->message);
380 return;
381 }
382
383 if (cb->reusingConnectionSocket &&
384 (socketDataHolder->type == (char)MSG_TYPE_RESPONSE || socketDataHolder->type == (char)MSG_TYPE_EXCEPTION)) {
385 ResponseListener *listener = getResponseListener(cb, socketDataHolder->requestId);
386 if (listener != 0) {
387 /* This is a response for a request (no callback for us) */
388 MsgRequestInfo *msgRequestInfoP = listener->msgRequestInfoP;
389 removeResponseListener(cb, socketDataHolder->requestId);
390 listener->responseEventFp(msgRequestInfoP, socketDataHolder);
391 freeBlobHolderContent(&socketDataHolder->blob);
392 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
393 "Forwarded message with requestId '%s' to response listener", socketDataHolder->requestId);
394 return;
395 }
396 else {
397 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
398 "PANIC: Did not expect an INVOCATION '%c'='%d' as a callback",
399 socketDataHolder->type, (int)socketDataHolder->type);
400 freeBlobHolderContent(&socketDataHolder->blob);
401 return;
402 }
403 }
404
405 msgUnitArrP = parseMsgUnitArr(socketDataHolder->blob.dataLen, socketDataHolder->blob.data);
406 freeBlobHolderContent(&(socketDataHolder->blob));
407
408 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
409 "Received requestId '%s' callback %s()",
410 socketDataHolder->requestId, socketDataHolder->methodName);
411
412 if (strcmp(socketDataHolder->methodName, XMLBLASTER_PING) == 0) {
413 size_t i;
414 for (i=0; i<msgUnitArrP->len; i++) {
415 msgUnitArrP->msgUnitArr[i].responseQos = strcpyAlloc("<qos/>");
416 }
417 sendResponse(cb, socketDataHolder, msgUnitArrP);
418 freeMsgUnitArr(msgUnitArrP);
419 }
420 else if (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE) == 0 ||
421 strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0) {
422 if (cb->updateCb != 0) { /* Client has registered to receive callback messages? */
423 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
424 "Calling client %s() for requestId '%s' ...",
425 socketDataHolder->methodName, socketDataHolder->requestId);
426
427 strncpy0(msgUnitArrP->secretSessionId, socketDataHolder->secretSessionId, MAX_SESSIONID_LEN);
428 msgUnitArrP->isOneway = (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0);
429 cb->updateCb(msgUnitArrP, cb, xmlBlasterException, socketDataHolder);
430 }
431 else { /* Unexpected update arrived, the client was not interested, see similar behavior in XmlBlasterAccess.java:update() */
432 size_t i;
433 for (i=0; i<msgUnitArrP->len; i++) {
434 msgUnitArrP->msgUnitArr[i].responseQos = strcpyAlloc("<qos><state id='OK'/></qos>");
435 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
436 "Ignoring unexpected %s() message as client has not registered a callback, requestId is '%s' ...",
437 socketDataHolder->methodName, socketDataHolder->requestId);
438 }
439 sendResponseOrException(true, cb, socketDataHolder, msgUnitArrP, xmlBlasterException);
440 }
441 }
442 else {
443 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
444 "Received unknown callback methodName=%s", socketDataHolder->methodName);
445 }
446
447 }
448
449
450 /**
451 * The run method of the two threads (TCP or UDP).
452 * <p />
453 * The caller must do a pthread_join or pthread_detach to avoid leaking,
454 * <br />
455 * see freeXmlBlasterAccessUnparsed() for TCP
456 * or runCallbackServer() for UDP
457 * <p />
458 * Set cb->stopListenLoop to false to end the thread
459 */
460 static int listenLoop(ListenLoopArgs* ls)
461 {
462 int rc;
463 CallbackServerUnparsed *cb = ls->cb;
464 bool udp = ls->udp;
465 XmlBlasterException xmlBlasterException;
466 SocketDataHolder socketDataHolder;
467 bool success;
468 bool useUdpForOneway = cb->socketUdp != -1;
469 for(;;) {
470 memset(&xmlBlasterException, 0, sizeof(XmlBlasterException));
471 /* Here we block until a message arrives, see parseSocketData() */
472 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
473 "Going to block on socket read until a new message arrives ...");
474 if (cb->stopListenLoop) break;
475 memset(&socketDataHolder, 0, sizeof(SocketDataHolder));
476 success = readMessage(cb, &socketDataHolder, &xmlBlasterException, udp);
477 if (cb->stopListenLoop) {
478 freeBlobHolderContent(&socketDataHolder.blob);
479 break;
480 }
481 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__, "%s arrived, success=%s", udp ? "UDP" : "TCP", success ? "true" : "false -> EOF");
482
483 if (useUdpForOneway) {
484 rc = pthread_mutex_lock(&cb->listenMutex);
485 if (rc != 0) /* EINVAL */
486 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_lock() returned %d.", rc);
487 }
488
489 handleMessage(cb, &socketDataHolder, &xmlBlasterException, success);
490
491 if (useUdpForOneway) {
492 rc = pthread_mutex_unlock(&cb->listenMutex);
493 if (rc != 0) /* EPERM */
494 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_unlock() returned %d.", rc);
495 }
496
497 if (cb->stopListenLoop || !success)
498 break;
499 }
500 /*pthread_exit(NULL);*/
501 return 0;
502 }
503
504
505 /**
506 * Started by XmlBlasterAccessUnparsed.c pthread_create(runCallbackServer).
507 * <p />
508 * Open a socket and only leaves when the connection is lost (on EOF),
509 * in this case implicit pthread_exit() is called.
510 * <p />
511 * xmlBlaster will connect and receive callback messages.
512 * @return 0 on success, 1 on error. The return value is the exit value returned by pthread_join()
513 */
514 static int runCallbackServer(CallbackServerUnparsed *cb)
515 {
516 int rc;
517 int retVal = 0;
518
519 const bool useUdpForOneway = cb->socketUdp != -1;
520
521 cb->threadIsAlive = true;
522 cb->_threadIsAliveOnce = true;
523
524 if (cb->listenSocket == -1) {
525 if (createCallbackServer(cb) == false) {
526 cb->threadIsAlive = false;
527 return 1;
528 }
529 }
530 else {
531 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
532 "Reusing connection socket to tunnel callback messages");
533 }
534
535 if (useUdpForOneway) {
536 ListenLoopArgs* tcpLoop = 0;
537 ListenLoopArgs* udpLoop = 0;
538
539 /* We need to create two threads: one for TCP and one for the UDP callback listener */
540 pthread_t tcpListenThread, udpListenThread;
541
542 rc = pthread_mutex_init(&cb->listenMutex, NULL); /* rc is always 0 */
543
544 tcpLoop = (ListenLoopArgs*)malloc(sizeof(ListenLoopArgs)); tcpLoop->cb = cb; tcpLoop->udp = false;
545 rc = pthread_create(&tcpListenThread, NULL, (void * (*)(void *))listenLoop, tcpLoop);
546
547 udpLoop = (ListenLoopArgs*)malloc(sizeof(ListenLoopArgs)); udpLoop->cb = cb; udpLoop->udp = true;
548 rc = pthread_create(&udpListenThread, NULL, (void * (*)(void *))listenLoop, udpLoop);
549
550 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
551 "Waiting to join tcpListenThread ...");
552 pthread_join(tcpListenThread, NULL);
553 free(tcpLoop);
554
555 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
556 "Waiting to join udpListenThread ...");
557 pthread_join(udpListenThread, NULL);
558 free(udpLoop);
559
560 rc = pthread_mutex_destroy(&cb->listenMutex);
561 if (rc != 0) /* EBUSY */
562 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_destroy() returned %d, we ignore it", rc);
563 }
564 else {
565 /* TCP only: no separate thread is needed (is called from XmlBlasterAccessUnparsed:pthread_create) */
566 ListenLoopArgs tcpLoop; tcpLoop.cb = cb; tcpLoop.udp = false;
567 retVal = listenLoop(&tcpLoop);
568 }
569
570 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
571 "Callbackserver thread is dying now ...");
572 cb->threadIsAlive = false; /* cb can be freed now */
573 return retVal;
574 }
575
576 /**
577 * Called from separate thread.
578 * Is only called if we start a dedicated callback server (not tunneling
579 * through the connection socket).
580 * @return true The callback server is started, false on error
581 */
582 static bool createCallbackServer(CallbackServerUnparsed *cb)
583 {
584 socklen_t cli_len;
585 struct hostent hostbuf, *hostP = NULL;
586 struct sockaddr_in serv_addr, cli_addr;
587 char *tmphstbuf=NULL;
588 size_t hstbuflen=0;
589 char serverHostName[256];
590 char errP[MAX_ERRNO_LEN];
591 if (cb->hostCB == 0) {
592 strcpyRealloc(&cb->hostCB, "localhost");
593 if (gethostname(serverHostName, 125) == 0)
594 strcpyRealloc(&cb->hostCB, serverHostName);
595 }
596
597 if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
598 "Starting callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d ...",
599 cb->hostCB, cb->portCB);
600
601 /*
602 * Get a socket to work with.
603 */
604 if ((cb->listenSocket = (int)socket(AF_INET, SOCK_STREAM, 0)) < 0) {
605 if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
606 "Failed creating socket for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
607 cb->hostCB, cb->portCB);
608 cb->threadIsAlive = false;
609 return false;
610 }
611
612 /*
613 * Create the address we will be binding to.
614 */
615 serv_addr.sin_family = AF_INET;
616 *errP = 0;
617 hostP = gethostbyname_re(cb->hostCB, &hostbuf, &tmphstbuf, &hstbuflen, errP);
618
619 if (*errP != 0) {
620 char message[EXCEPTIONSTRUCT_MESSAGE_LEN];
621 SNPRINTF(message, XMLBLASTEREXCEPTION_MESSAGE_LEN,
622 "[%.100s:%d] Lookup xmlBlaster failed, %s",
623 __FILE__, __LINE__, errP);
624 cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__, message);
625 *errP = 0;
626 }
627
628 if (hostP != NULL) {
629 serv_addr.sin_addr.s_addr = ((struct in_addr *)(hostP->h_addr))->s_addr; /*inet_addr("192.168.1.2"); */
630 free(tmphstbuf);
631 }
632 else
633 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
634 serv_addr.sin_port = htons((u_short)cb->portCB);
635
636 if (bind(cb->listenSocket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
637 if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
638 "Failed binding port for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
639 cb->hostCB, cb->portCB);
640 return false;
641 }
642
643 /*
644 * Listen on the socket.
645 */
646 if (listen(cb->listenSocket, 5) < 0) {
647 if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
648 "Failed creating listener for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
649 cb->hostCB, cb->portCB);
650 return false;
651 }
652
653 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
654 "[CallbackServerUnparsed] Waiting for xmlBlaster to connect ...");
655
656 /*
657 * Accept connections. When we accept one, ns
658 * will be connected to the client. cli_addr will
659 * contain the address of the client.
660 */
661 cli_len = (socklen_t)sizeof(cli_addr);
662 if ((cb->acceptSocket = (int)accept(cb->listenSocket, (struct sockaddr *)&cli_addr, &cli_len)) < 0) {
663 if (cb->logLevel>=XMLBLASTER_LOG_ERROR) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
664 "[CallbackServerUnparsed] accept failed");
665 cb->threadIsAlive = false;
666 return false;
667 }
668 if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
669 "[CallbackServerUnparsed] XmlBlaster connected from %s:%hd",
670 inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);
671 return true;
672 }
673
674 static bool isListening(CallbackServerUnparsed *cb)
675 {
676 if (cb->listenSocket > -1) {
677 return true;
678 }
679 return false;
680 }
681
682 /**
683 * Parse the update message from xmlBlaster.
684 * <p>
685 * This method blocks until data arrives.
686 * </p>
687 * The socketDataHolder holds all informations about the returned data from xmlBlaster,
688 * on error the exception struct is filled.
689 *
690 * @param cb The 'this' pointer
691 * @param socketDataHolder You need to free(socketDataHolder->data) if return is 'true'.
692 * @param exception Contains the exception thrown (on error only)
693 * @param udp If UDP to use
694 * @return true if OK or on exception, false on EOF
695 */
696 static bool readMessage(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception, bool udp)
697 {
698 return parseSocketData(udp ? cb->socketUdp : cb->acceptSocket, &cb->readFromSocket, socketDataHolder,
699 exception, &cb->stopListenLoop, udp, cb->logLevel >= XMLBLASTER_LOG_DUMP);
700 }
701
702 /** A helper to cast to SocketDataHolder */
703 static void voidSendResponse(CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP)
704 {
705 sendResponse(cb, (SocketDataHolder *)socketDataHolder, msgUnitArrP);
706 }
707
708 static void sendResponse(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP)
709 {
710 char *rawMsg;
711 size_t rawMsgLen;
712 size_t dataLen = 0;
713 char *data = 0;
714 size_t i;
715 MsgUnit msgUnit; /* we (mis)use MsgUnit for simple transformation of the exception into a raw blob */
716 bool allocated = false;
717 memset(&msgUnit, 0, sizeof(MsgUnit));
718
719 for (i=0; i<msgUnitArrP->len; i++) {
720 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
721 "Returning the UpdateReturnQos '%s' to the server.",
722 msgUnitArrP->msgUnitArr[i].responseQos);
723
724 if (msgUnitArrP->msgUnitArr[i].responseQos != 0) {
725 msgUnit.qos = msgUnitArrP->msgUnitArr[i].responseQos;
726 }
727 else {
728 msgUnit.qos = strcpyAlloc("<qos/>");
729 allocated = true;
730 }
731
732 if (data == 0) {
733 BlobHolder blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
734 data = blob.data;
735 dataLen = blob.dataLen;
736 }
737 else {
738 BlobHolder blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
739 data = (char *)realloc(data, dataLen+blob.dataLen);
740 memcpy(data+dataLen, blob.data, blob.dataLen);
741 dataLen += blob.dataLen;
742 free(blob.data);
743 }
744 }
745
746 rawMsg = encodeSocketMessage(MSG_TYPE_RESPONSE, socketDataHolder->requestId,
747 socketDataHolder->methodName, socketDataHolder->secretSessionId,
748 data, dataLen, cb->logLevel >= XMLBLASTER_LOG_DUMP, &rawMsgLen);
749 free(data);
750
751 /*ssize_t numSent =*/(void) cb->writeToSocket.writeToSocketFuncP(cb->updateCbUserData, cb->acceptSocket, rawMsg, (int)rawMsgLen);
752
753 free(rawMsg);
754
755 if (allocated) free((char *)msgUnit.qos);
756 }
757
758 static void voidSendXmlBlasterException(CallbackServerUnparsed *cb, void *socketDataHolder, XmlBlasterException *exception)
759 {
760 sendXmlBlasterException(cb, (SocketDataHolder *)socketDataHolder, exception);
761 }
762
763 static void sendXmlBlasterException(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception)
764 {
765 size_t currpos = 0;
766 char *rawMsg;
767 size_t rawMsgLen;
768 BlobHolder blob;
769 MsgUnit msgUnit; /* we (mis)use MsgUnit for simple transformation of the exception into a raw blob */
770 memset(&msgUnit, 0, sizeof(MsgUnit));
771
772 msgUnit.qos = exception->errorCode;
773
774 /* see XmlBlasterException.toByteArr() and parseByteArr() */
775 msgUnit.contentLen = strlen(exception->errorCode) + strlen(exception->message) + 11;
776 msgUnit.content = (char *)calloc(msgUnit.contentLen, sizeof(char));
777
778 memcpy((char *)msgUnit.content, exception->errorCode, strlen(exception->errorCode));
779 currpos = strlen(exception->errorCode) + 4;
780
781 memcpy((char *)msgUnit.content+currpos, exception->message, strlen(exception->message));
782
783 blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
784
785 rawMsg = encodeSocketMessage(MSG_TYPE_EXCEPTION, socketDataHolder->requestId,
786 socketDataHolder->methodName, socketDataHolder->secretSessionId,
787 blob.data, blob.dataLen, cb->logLevel >= XMLBLASTER_LOG_DUMP, &rawMsgLen);
788 free(blob.data);
789 free((char *)msgUnit.content);
790
791 /*ssize_t numSent =*/(void) cb->writeToSocket.writeToSocketFuncP(cb->updateCbUserData, cb->acceptSocket, rawMsg, (int)rawMsgLen);
792
793 free(rawMsg);
794 }
795
796 /**
797 * A helper to cast to SocketDataHolder
798 * Frees msgUnitArrP
799 */
800 static void voidSendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception)
801 {
802 sendResponseOrException(success, cb, (SocketDataHolder *)socketDataHolder, msgUnitArrP, exception);
803 }
804
805 /**
806 * Takes care of both successful responses as well as exceptions
807 * Frees msgUnitArrP
808 */
809 static void sendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception)
810 {
811 if (! (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0)) {
812 if (success == true) {
813 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
814 "update(): Sending response for requestId '%s'", socketDataHolder->requestId);
815 sendResponse(cb, socketDataHolder, msgUnitArrP);
816 }
817 else {
818 if (*(exception->errorCode) == 0) {
819 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
820 "update(): We don't return anything for requestId '%s', the return message will come later by the client update dispatcher thread", socketDataHolder->requestId);
821 }
822 else {
823 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
824 "update(): Throwing the XmlBlasterException '%s' back to the server:\n%s",
825 exception->errorCode, exception->message);
826 sendXmlBlasterException(cb, socketDataHolder, exception);
827 }
828 }
829 }
830
831 freeMsgUnitArr(msgUnitArrP);
832 }
833
834 /**
835 * Force closing socket
836 */
837 static void closeAcceptSocket(CallbackServerUnparsed *cb)
838 {
839 /* We close even if cb->reusingConnectionSocket is set
840 to react instantly on EOF from server side.
841 Otherwise the client thread would block until socket response timeout happens (one minute)
842 */
843 if (cb->acceptSocket != -1) {
844 closeSocket(cb->acceptSocket);
845 cb->acceptSocket = -1;
846 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
847 "Closed accept socket");
848 }
849 }
850
851 /**
852 * Used internally only to close the socket, calling multiple times makes no harm.
853 * <p />
854 * Closes socket, tells listenLoop to terminate but does not wait on thread termination
855 * and does not destroy cb.
856 * <p />
857 *
858 */
859 static void shutdownCallbackServer(CallbackServerUnparsed *cb)
860 {
861 if (cb == 0) return;
862
863 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
864 "Shutdown callback server stopListenLoop=%s (changes now to true), reusingConnectionSocket=%s", (cb->stopListenLoop?"true":"false"), (cb->reusingConnectionSocket?"true":"false"));
865
866 cb->stopListenLoop = true;
867
868 if (cb->hostCB != 0) {
869 free(cb->hostCB);
870 cb->hostCB = 0;
871 }
872
873 if (cb->reusingConnectionSocket) {
874 return; /* not our duty, we only have borrowed the socket from the client side connection */
875 }
876
877
878 closeAcceptSocket(cb);
879
880 if (isListening(cb)) {
881 closeSocket(cb->listenSocket);
882 cb->listenSocket = -1;
883 if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
884 "Closed listener socket");
885 }
886
887 cb->readFromSocket.numReadFuncP = 0;
888 }
889
890 const char *callbackServerRawUsage(void)
891 {
892 return
893 "\n -dispatch/callback/plugin/socket/hostname [localhost]"
894 "\n The IP where to establish the callback server."
895 "\n Can be useful on multi homed hosts."
896 "\n -dispatch/callback/plugin/socket/port [7611]"
897 "\n The port of the callback server.";
898 }
899
900 #ifdef USE_MAIN_CB
901 /**
902 * Here we receive the callback messages from xmlBlaster
903 */
904 bool myUpdate(MsgUnitArr *msgUnitArr, void *userData, XmlBlasterException *xmlBlasterException, SocketDataHandler socketDataHandler)
905 {
906 size_t i;
907 bool testException = false;
908 for (i=0; i<msgUnitArr->len; i++) {
909 char *xml = messageUnitToXml(&msgUnitArr->msgUnitArr[i]);
910 printf("client.update(): Asynchronous message update arrived:%s\n", xml);
911 free(xml);
912 msgUnitArr->msgUnitArr[i].responseQos = strcpyAlloc("<qos></qos>"); /* Return QoS: Everything is OK */
913 }
914 if (testException) {
915 strncpy0(xmlBlasterException->errorCode, "user.notWanted", XMLBLASTEREXCEPTION_ERRORCODE_LEN);
916 strncpy0(xmlBlasterException->message, "I don't want these messages", XMLBLASTEREXCEPTION_MESSAGE_LEN);
917 return false;
918 }
919 return true;
920 }
921
922 /**
923 * Invoke: CallbackServerUnparsed -logLevel TRACE
924 */
925 int main(int argc, char** argv)
926 {
927 int iarg;
928 CallbackServerUnparsed *cb;
929
930 for (iarg=0; iarg < argc; iarg++) {
931 if (strcmp(argv[iarg], "-help") == 0 || strcmp(argv[iarg], "--help") == 0) {
932 const char *pp =
933 "\n -logLevel ERROR | WARN | INFO | TRACE [WARN]"
934 "\n\nExample:"
935 "\n CallbackServerUnparsed -logLevel TRACE -dispatch/callback/plugin/socket/hostname server.mars.universe";
936 printf("Usage:\n%s%s\n", callbackServerRawUsage(), pp);
937 exit(1);
938 }
939 }
940
941 cb = getCallbackServerUnparsed(argc, argv, myUpdate, 0);
942 printf("[main] Created CallbackServerUnparsed instance, creating listener on socket://%s:%d...\n", cb->hostCB, cb->portCB);
943 cb->runCallbackServer(cb); /* blocks on socket listener */
944
945 /* This code is reached only on socket EOF */
946
947 printf("[main] Socket listener is shutdown\n");
948 freeCallbackServerUnparsed(&cb);
949 return 0;
950 }
951 #endif /* USE_MAIN_CB */
syntax highlighted by Code2HTML, v. 0.9.1