1# -*- coding: utf-8 -*-
2"""
3Unittests for http serving module
4"""
5
6import sys
7
8import unittest
9
10from urllib.parse import urlsplit, quote, quote_plus, unquote, unquote_plus
11
12
13import os
14import time
15import tempfile
16import shutil
17import socket
18import errno
19
20try:
21    import simplejson as json
22except ImportError:
23    import json
24
25# Import ioflo libs
26from ioflo.aid.sixing import *
27from ioflo.aid.odicting import odict
28from ioflo.aid.timing import Timer, StoreTimer
29from ioflo.aid.consoling import getConsole
30from ioflo.base import storing
31
32from ioflo.aio import wiring
33from ioflo.aio.http import httping, clienting, serving
34
35console = getConsole()
36
37
38def setUpModule():
39    pass
40
41def tearDownModule():
42    pass
43
44
45class BasicTestCase(unittest.TestCase):
46    """
47    Test Case
48    """
49
50    def setUp(self):
51        """
52
53        """
54        console.reinit(verbosity=console.Wordage.profuse)
55
56        tempdirpath = os.path.dirname(
57                        os.path.dirname(
58                            os.path.dirname(
59                                os.path.abspath(
60                                    sys.modules.get(__name__).__file__))))
61        self.certdirpath = os.path.join(tempdirpath, 'test', 'tls', 'certs')
62
63    def tearDown(self):
64        """
65
66        """
67        console.reinit(verbosity=console.Wordage.concise)
68
69    def testHttpError(self):
70        """
71        Test HTTPError class
72        """
73        console.terse("{0}\n".format(self.testHttpError.__doc__))
74
75        error = httping.HTTPError(status=400)
76        self.assertEqual(error.status, 400)
77        self.assertEqual(error.reason, 'Bad Request')
78        self.assertEqual(error.title, "")
79        self.assertEqual(error.detail, "")
80        self.assertIs(error.fault, None)
81        self.assertEqual(error.headers, odict())
82
83        body = error.render()
84        self.assertEqual(body, b'400 Bad Request\n\n\n')
85        body = error.render(jsonify=True)
86        self.assertEqual(body, (b'{\n  "status": 400,\n  "reason": "Bad Request",\n'
87                                b'  "title": "",\n  "detail":'
88                                b' "",\n  "fault": null\n}')
89                               )
90
91        error = httping.HTTPError(status=700,
92                                  title="Validation Error",
93                                  detail="Bad mojo",
94                                  fault=50,
95                                  headers=odict(Accept='application/json'))
96
97        self.assertEqual(error.status, 700)
98        self.assertEqual(error.reason, 'Unknown')
99        self.assertEqual(error.title, "Validation Error")
100        self.assertEqual(error.detail, "Bad mojo")
101        self.assertIs(error.fault, 50)
102        self.assertEqual(error.headers, odict(Accept='application/json'))
103
104        body = error.render()
105        self.assertEqual(body, b'700 Unknown\nValidation Error\nBad mojo\n50')
106        body = error.render(jsonify=True)
107        self.assertEqual(body, (b'{\n  "status": 700,\n  "reason": "Unknown",\n'
108                                b'  "title": "Validation Error",'
109                                b'\n  "detail": "Bad mojo",\n  "fault": 50\n}'))
110
111    def testPorterServiceEcho(self):
112        """
113        Test Porter service request response of echo non blocking
114        """
115        console.terse("{0}\n".format(self.testPorterServiceEcho.__doc__))
116
117
118
119        store = storing.Store(stamp=0.0)
120
121        console.terse("{0}\n".format("Building Valet ...\n"))
122        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
123        result = wireLogAlpha.reopen()
124
125        alpha = serving.Porter(port = 6101,
126                              bufsize=131072,
127                              wlog=wireLogAlpha,
128                              store=store)
129        self.assertIs(alpha.servant.reopen(), True)
130        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
131        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
132
133        console.terse("{0}\n".format("Building Patron ...\n"))
134        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
135        result = wireLogBeta.reopen()
136
137        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
138
139        beta = clienting.Patron(bufsize=131072,
140                                     wlog=wireLogBeta,
141                                     store=store,
142                                     path=path,
143                                     reconnectable=True,
144                                     )
145
146        self.assertIs(beta.connector.reopen(), True)
147        self.assertIs(beta.connector.accepted, False)
148        self.assertIs(beta.connector.connected, False)
149        self.assertIs(beta.connector.cutoff, False)
150
151        request = odict([('method', u'GET'),
152                         ('path', u'/echo?name=fame'),
153                         ('qargs', odict()),
154                         ('fragment', u''),
155                         ('headers', odict([('Accept', 'application/json'),
156                                            ('Content-Length', 0)])),
157                        ])
158
159        beta.requests.append(request)
160
161        while (beta.requests or beta.connector.txes or not beta.responses or
162               not alpha.servant.ixes or not alpha.idle()):
163            alpha.serviceAll()
164            time.sleep(0.05)
165            beta.serviceAll()
166            time.sleep(0.05)
167
168        self.assertIs(beta.connector.accepted, True)
169        self.assertIs(beta.connector.connected, True)
170        self.assertIs(beta.connector.cutoff, False)
171
172        self.assertEqual(len(alpha.servant.ixes), 1)
173        self.assertEqual(len(alpha.stewards), 1)
174        requestant = alpha.stewards.values()[0].requestant
175        self.assertEqual(requestant.method, request['method'])
176        self.assertEqual(requestant.url, request['path'])
177        self.assertEqual(requestant.headers, {'accept': 'application/json',
178                                                'accept-encoding': 'identity',
179                                                'content-length': '0',
180                                                'host': 'localhost:6101'})
181
182
183        self.assertEqual(len(beta.responses), 1)
184        response = beta.responses.popleft()
185        self.assertEqual(response['data'],{'body': '',
186                                        'data': None,
187                                        'fragment': '',
188                                        'headers': {'accept': 'application/json',
189                                                    'accept-encoding': 'identity',
190                                                    'content-length': '0',
191                                                    'host': 'localhost:6101'},
192                                        'method': 'GET',
193                                        'path': '/echo',
194                                        'qargs': {'name': 'fame'},
195                                        'version': 'HTTP/1.1'})
196
197        responder = alpha.stewards.values()[0].responder
198        self.assertEqual(responder.status, response['status'])
199        self.assertEqual(responder.headers, response['headers'])
200
201        alpha.servant.closeAll()
202        beta.connector.close()
203
204        wireLogAlpha.close()
205        wireLogBeta.close()
206
207    def testValetServiceBasic(self):
208        """
209        Test Valet WSGI service request response
210        """
211        console.terse("{0}\n".format(self.testValetServiceBasic.__doc__))
212
213        store = storing.Store(stamp=0.0)
214
215        def wsgiApp(environ, start_response):
216            start_response('200 OK', [('Content-type','text/plain'),
217                                      ('Content-length', '12')])
218            return [b"Hello World!"]
219
220        console.terse("{0}\n".format("Building Valet ...\n"))
221        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
222        result = wireLogAlpha.reopen()
223
224        alpha = serving.Valet(port = 6101,
225                              bufsize=131072,
226                              wlog=wireLogAlpha,
227                              store=store,
228                              app=wsgiApp)
229        self.assertIs(alpha.servant.reopen(), True)
230        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
231        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
232
233        console.terse("{0}\n".format("Building Patron ...\n"))
234        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
235        result = wireLogBeta.reopen()
236
237        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
238
239        beta = clienting.Patron(bufsize=131072,
240                                     wlog=wireLogBeta,
241                                     store=store,
242                                     path=path,
243                                     reconnectable=True,
244                                     )
245
246        self.assertIs(beta.connector.reopen(), True)
247        self.assertIs(beta.connector.accepted, False)
248        self.assertIs(beta.connector.connected, False)
249        self.assertIs(beta.connector.cutoff, False)
250
251        request = odict([('method', u'GET'),
252                         ('path', u'/echo?name=fame'),
253                         ('qargs', odict()),
254                         ('fragment', u''),
255                         ('headers', odict([('Accept', 'application/json'),
256                                            ('Content-Length', 0)])),
257                        ])
258
259        beta.requests.append(request)
260
261        while (beta.requests or beta.connector.txes or not beta.responses or
262               not alpha.idle()):
263            alpha.serviceAll()
264            time.sleep(0.05)
265            beta.serviceAll()
266            time.sleep(0.05)
267
268        self.assertIs(beta.connector.accepted, True)
269        self.assertIs(beta.connector.connected, True)
270        self.assertIs(beta.connector.cutoff, False)
271
272        self.assertEqual(len(alpha.servant.ixes), 1)
273        self.assertEqual(len(alpha.reqs), 1)
274        self.assertEqual(len(alpha.reps), 1)
275        requestant = alpha.reqs.values()[0]
276        self.assertEqual(requestant.method, request['method'])
277        self.assertEqual(requestant.url, request['path'])
278        self.assertEqual(requestant.headers, {'accept': 'application/json',
279                                                'accept-encoding': 'identity',
280                                                'content-length': '0',
281                                                'host': 'localhost:6101'})
282
283
284        self.assertEqual(len(beta.responses), 1)
285        response = beta.responses.popleft()
286        self.assertEqual(response['body'],bytearray(b'Hello World!'))
287        self.assertEqual(response['status'], 200)
288
289        responder = alpha.reps.values()[0]
290        self.assertTrue(responder.status.startswith, str(response['status']))
291        self.assertEqual(responder.headers, response['headers'])
292
293        alpha.servant.closeAll()
294        beta.connector.close()
295
296        wireLogAlpha.close()
297        wireLogBeta.close()
298
299    def testValetServiceBottle(self):
300        """
301        Test Valet WSGI service request response
302        """
303        console.terse("{0}\n".format(self.testValetServiceBottle.__doc__))
304
305        try:
306            import bottle
307        except ImportError as ex:
308            console.terse("Bottle not available.\n")
309            return
310
311        store = storing.Store(stamp=0.0)
312
313        app = bottle.default_app() # create bottle app
314
315        @app.get('/echo')
316        @app.get('/echo/<action>')
317        @app.post('/echo')
318        @app.post('/echo/<action>')
319        def echoGet(action=None):
320            """
321            Echo back request data
322            """
323            query = dict(bottle.request.query.items())
324            body = bottle.request.json
325            raw = bottle.request.body.read()
326            form = odict(bottle.request.forms)
327
328            data = odict(verb=bottle.request.method,
329                        url=bottle.request.url,
330                        action=action,
331                        query=query,
332                        form=form,
333                        content=body)
334            return data
335
336
337        console.terse("{0}\n".format("Building Valet ...\n"))
338        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
339        result = wireLogAlpha.reopen()
340
341        alpha = serving.Valet(port = 6101,
342                              bufsize=131072,
343                              wlog=wireLogAlpha,
344                              store=store,
345                              app=app)
346        self.assertIs(alpha.servant.reopen(), True)
347        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
348        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
349
350        console.terse("{0}\n".format("Building Patron ...\n"))
351        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
352        result = wireLogBeta.reopen()
353
354        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
355
356        beta = clienting.Patron(bufsize=131072,
357                                     wlog=wireLogBeta,
358                                     store=store,
359                                     path=path,
360                                     reconnectable=True,
361                                     )
362
363        self.assertIs(beta.connector.reopen(), True)
364        self.assertIs(beta.connector.accepted, False)
365        self.assertIs(beta.connector.connected, False)
366        self.assertIs(beta.connector.cutoff, False)
367
368        request = odict([('method', u'GET'),
369                         ('path', u'/echo?name=fame'),
370                         ('qargs', odict()),
371                         ('fragment', u''),
372                         ('headers', odict([('Accept', 'application/json'),
373                                            ('Content-Length', 0)])),
374                        ])
375
376        beta.requests.append(request)
377        timer = StoreTimer(store, duration=1.0)
378        while (beta.requests or beta.connector.txes or not beta.responses or
379               not alpha.idle()):
380            alpha.serviceAll()
381            time.sleep(0.05)
382            beta.serviceAll()
383            time.sleep(0.05)
384            store.advanceStamp(0.1)
385
386        self.assertIs(beta.connector.accepted, True)
387        self.assertIs(beta.connector.connected, True)
388        self.assertIs(beta.connector.cutoff, False)
389
390        self.assertEqual(len(alpha.servant.ixes), 1)
391        self.assertEqual(len(alpha.reqs), 1)
392        self.assertEqual(len(alpha.reps), 1)
393        requestant = alpha.reqs.values()[0]
394        self.assertEqual(requestant.method, request['method'])
395        self.assertEqual(requestant.url, request['path'])
396        self.assertEqual(requestant.headers, {'accept': 'application/json',
397                                                'accept-encoding': 'identity',
398                                                'content-length': '0',
399                                                'host': 'localhost:6101'})
400
401        self.assertEqual(len(beta.responses), 1)
402        response = beta.responses.popleft()
403        self.assertEqual(response['status'], 200)
404        self.assertEqual(response['reason'], 'OK')
405        self.assertEqual(response['body'], bytearray(b'{"verb": "GET", "url": "http://localhost:6101/echo?name=fame", "'
406                                                        b'action": null, "query": {"name": "fame"}, "form": {}, "content":'
407                                                        b' null}')
408                                              )
409        self.assertEqual(response['data'],{'action': None,
410                                            'content': None,
411                                            'form': {},
412                                            'query': {'name': 'fame'},
413                                            'url': 'http://localhost:6101/echo?name=fame',
414                                            'verb': 'GET'},)
415
416        responder = alpha.reps.values()[0]
417        self.assertTrue(responder.status.startswith, str(response['status']))
418        self.assertEqual(responder.headers, response['headers'])
419
420        alpha.servant.closeAll()
421        beta.connector.close()
422
423        wireLogAlpha.close()
424        wireLogBeta.close()
425
426    def testValetServiceBottleNoContentLength(self):
427        """
428        Test Valet WSGI service request response no content-length in request
429        """
430        console.terse("{0}\n".format(self.testValetServiceBottleNoContentLength.__doc__))
431
432        try:
433            import bottle
434        except ImportError as ex:
435            console.terse("Bottle not available.\n")
436            return
437
438        store = storing.Store(stamp=0.0)
439
440        app = bottle.default_app() # create bottle app
441
442        @app.get('/echo')
443        @app.get('/echo/<action>')
444        @app.post('/echo')
445        @app.post('/echo/<action>')
446        def echoGet(action=None):
447            """
448            Echo back request data
449            """
450            query = dict(bottle.request.query.items())
451            body = bottle.request.json
452            raw = bottle.request.body.read()
453            form = odict(bottle.request.forms)
454
455            data = odict(verb=bottle.request.method,
456                        url=bottle.request.url,
457                        action=action,
458                        query=query,
459                        form=form,
460                        content=body)
461            return data
462
463
464        console.terse("{0}\n".format("Building Valet ...\n"))
465        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
466        result = wireLogAlpha.reopen()
467
468        alpha = serving.Valet(port = 6101,
469                              bufsize=131072,
470                              wlog=wireLogAlpha,
471                              store=store,
472                              app=app)
473        self.assertIs(alpha.servant.reopen(), True)
474        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
475        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
476
477        console.terse("{0}\n".format("Building Patron ...\n"))
478        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
479        result = wireLogBeta.reopen()
480
481        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
482
483        beta = clienting.Patron(bufsize=131072,
484                                     wlog=wireLogBeta,
485                                     store=store,
486                                     path=path,
487                                     reconnectable=True,
488                                     )
489
490        self.assertIs(beta.connector.reopen(), True)
491        self.assertIs(beta.connector.accepted, False)
492        self.assertIs(beta.connector.connected, False)
493        self.assertIs(beta.connector.cutoff, False)
494
495        request = odict([('method', u'GET'),
496                         ('path', u'/echo?name=fame'),
497                         ('qargs', odict()),
498                         ('fragment', u''),
499                         ('headers', odict([('Accept', 'application/json'),
500                                            ])),
501                        ])
502
503        beta.requests.append(request)
504        timer = StoreTimer(store, duration=1.0)
505        while (beta.requests or beta.connector.txes or not beta.responses or
506               not alpha.idle()):
507            alpha.serviceAll()
508            time.sleep(0.05)
509            beta.serviceAll()
510            time.sleep(0.05)
511            store.advanceStamp(0.1)
512
513        self.assertIs(beta.connector.accepted, True)
514        self.assertIs(beta.connector.connected, True)
515        self.assertIs(beta.connector.cutoff, False)
516
517        self.assertEqual(len(alpha.servant.ixes), 1)
518        self.assertEqual(len(alpha.reqs), 1)
519        self.assertEqual(len(alpha.reps), 1)
520        requestant = alpha.reqs.values()[0]
521        self.assertEqual(requestant.method, request['method'])
522        self.assertEqual(requestant.url, request['path'])
523        self.assertEqual(requestant.headers, {'accept': 'application/json',
524                                                'accept-encoding': 'identity',
525                                                'host': 'localhost:6101'})
526
527        self.assertEqual(len(beta.responses), 1)
528        response = beta.responses.popleft()
529        self.assertEqual(response['status'], 200)
530        self.assertEqual(response['reason'], 'OK')
531        self.assertEqual(response['body'], bytearray(b'{"verb": "GET", "url": "http://localhost:6101/echo?name=fame", "'
532                                                        b'action": null, "query": {"name": "fame"}, "form": {}, "content":'
533                                                        b' null}'))
534        self.assertEqual(response['data'],{'action': None,
535                                            'content': None,
536                                            'form': {},
537                                            'query': {'name': 'fame'},
538                                            'url': 'http://localhost:6101/echo?name=fame',
539                                            'verb': 'GET'},)
540
541        responder = alpha.reps.values()[0]
542        self.assertTrue(responder.status.startswith, str(response['status']))
543        self.assertEqual(responder.headers, response['headers'])
544
545        alpha.servant.closeAll()
546        beta.connector.close()
547
548        wireLogAlpha.close()
549        wireLogBeta.close()
550
551    def testValetServiceBottleNonPersistent(self):
552        """
553        Test Valet WSGI service request response non persistent connection in request
554        """
555        console.terse("{0}\n".format(self.testValetServiceBottleNonPersistent.__doc__))
556
557        try:
558            import bottle
559        except ImportError as ex:
560            console.terse("Bottle not available.\n")
561            return
562
563        store = storing.Store(stamp=0.0)
564
565        app = bottle.default_app() # create bottle app
566
567        @app.get('/echo')
568        @app.get('/echo/<action>')
569        @app.post('/echo')
570        @app.post('/echo/<action>')
571        def echoGet(action=None):
572            """
573            Echo back request data
574            """
575            query = dict(bottle.request.query.items())
576            body = bottle.request.json
577            raw = bottle.request.body.read()
578            form = odict(bottle.request.forms)
579
580            data = odict(verb=bottle.request.method,
581                        url=bottle.request.url,
582                        action=action,
583                        query=query,
584                        form=form,
585                        content=body)
586            return data
587
588
589        console.terse("{0}\n".format("Building Valet ...\n"))
590        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
591        result = wireLogAlpha.reopen()
592
593        alpha = serving.Valet(port = 6101,
594                              bufsize=131072,
595                              wlog=wireLogAlpha,
596                              store=store,
597                              app=app)
598        self.assertIs(alpha.servant.reopen(), True)
599        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
600        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
601
602        console.terse("{0}\n".format("Building Patron ...\n"))
603        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
604        result = wireLogBeta.reopen()
605
606        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
607
608        beta = clienting.Patron(bufsize=131072,
609                                     wlog=wireLogBeta,
610                                     store=store,
611                                     path=path,
612                                     reconnectable=True,
613                                     )
614
615        self.assertIs(beta.connector.reopen(), True)
616        self.assertIs(beta.connector.accepted, False)
617        self.assertIs(beta.connector.connected, False)
618        self.assertIs(beta.connector.cutoff, False)
619
620        request = odict([('method', u'GET'),
621                         ('path', u'/echo?name=fame'),
622                         ('qargs', odict()),
623                         ('fragment', u''),
624                         ('headers', odict([('Accept', 'application/json'),
625                                            ('Connection', 'close')])),
626                        ])
627
628        beta.requests.append(request)
629        timer = StoreTimer(store, duration=1.0)
630        while (beta.requests or beta.connector.txes or not beta.responses or
631               not alpha.idle()):
632            alpha.serviceAll()
633            time.sleep(0.05)
634            beta.serviceAll()
635            time.sleep(0.05)
636            store.advanceStamp(0.1)
637
638        self.assertIs(beta.connector.accepted, True)
639        self.assertIs(beta.connector.connected, True)
640        self.assertIs(beta.connector.cutoff, False)
641
642        self.assertEqual(len(alpha.servant.ixes), 1)
643        self.assertEqual(len(alpha.reqs), 1)
644        self.assertEqual(len(alpha.reps), 1)
645        requestant = alpha.reqs.values()[0]
646        self.assertEqual(requestant.method, request['method'])
647        self.assertEqual(requestant.url, request['path'])
648        self.assertEqual(requestant.headers, {'accept': 'application/json',
649                                                'accept-encoding': 'identity',
650                                                'host': 'localhost:6101',
651                                                'connection': 'close',})
652
653        self.assertEqual(len(beta.responses), 1)
654        response = beta.responses.popleft()
655        self.assertEqual(response['status'], 200)
656        self.assertEqual(response['reason'], 'OK')
657        self.assertEqual(response['body'], bytearray(b'{"verb": "GET", "url": "http://localhost:6101/echo?name=fame", "'
658                                                        b'action": null, "query": {"name": "fame"}, "form": {}, "content":'
659                                                        b' null}'))
660        self.assertEqual(response['data'],{'action': None,
661                                            'content': None,
662                                            'form': {},
663                                            'query': {'name': 'fame'},
664                                            'url': 'http://localhost:6101/echo?name=fame',
665                                            'verb': 'GET'},)
666
667        responder = alpha.reps.values()[0]
668        self.assertTrue(responder.status.startswith, str(response['status']))
669        self.assertEqual(responder.headers, response['headers'])
670
671        alpha.servant.closeAll()
672        beta.connector.close()
673
674        wireLogAlpha.close()
675        wireLogBeta.close()
676
677    def testValetServiceBottleStream(self):
678        """
679        Test Valet WSGI service request response stream sse
680        """
681        console.terse("{0}\n".format(self.testValetServiceBottleStream.__doc__))
682
683        try:
684            import bottle
685        except ImportError as ex:
686            console.terse("Bottle not available.\n")
687            return
688
689        store = storing.Store(stamp=0.0)
690
691        app = bottle.default_app() # create bottle app
692
693        @app.get('/stream')
694        def streamGet():
695            """
696            Create test server sent event stream that sends count events
697            """
698            timer = StoreTimer(store, duration=2.0)
699            bottle.response.set_header('Content-Type',  'text/event-stream') #text
700            bottle.response.set_header('Cache-Control',  'no-cache')
701            # HTTP 1.1 servers detect text/event-stream and use Transfer-Encoding: chunked
702            # Set client-side auto-reconnect timeout, ms.
703            yield 'retry: 1000\n\n'
704            i = 0
705            yield 'id: {0}\n'.format(i)
706            i += 1
707            yield 'data: START\n\n'
708            n = 1
709            while not timer.expired:
710                yield 'id: {0}\n'.format(i)
711                i += 1
712                yield 'data: {0}\n\n'.format(n)
713                n += 1
714            yield "data: END\n\n"
715
716        console.terse("{0}\n".format("Building Valet ...\n"))
717        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
718        result = wireLogAlpha.reopen()
719
720        alpha = serving.Valet(port = 6101,
721                              bufsize=131072,
722                              wlog=wireLogAlpha,
723                              store=store,
724                              app=app)
725        self.assertIs(alpha.servant.reopen(), True)
726        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
727        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
728
729        console.terse("{0}\n".format("Building Patron ...\n"))
730        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
731        result = wireLogBeta.reopen()
732
733        path = "http://{0}:{1}/".format('localhost', alpha.servant.eha[1])
734
735        beta = clienting.Patron(bufsize=131072,
736                                     wlog=wireLogBeta,
737                                     store=store,
738                                     path=path,
739                                     reconnectable=True,
740                                     )
741
742        self.assertIs(beta.connector.reopen(), True)
743        self.assertIs(beta.connector.accepted, False)
744        self.assertIs(beta.connector.connected, False)
745        self.assertIs(beta.connector.cutoff, False)
746
747        request = odict([('method', u'GET'),
748                         ('path', u'/stream'),
749                         ('qargs', odict()),
750                         ('fragment', u''),
751                         ('headers', odict([('Accept', 'application/json'),
752                                            ('Content-Length', 0)])),
753                         ('body', None),
754                        ])
755
756        beta.requests.append(request)
757        timer = StoreTimer(store, duration=1.0)
758        while (not timer.expired):
759            alpha.serviceAll()
760            time.sleep(0.05)
761            beta.serviceAll()
762            time.sleep(0.05)
763            store.advanceStamp(0.1)
764
765        self.assertIs(beta.connector.accepted, True)
766        self.assertIs(beta.connector.connected, True)
767        self.assertIs(beta.connector.cutoff, False)
768
769        self.assertEqual(len(alpha.servant.ixes), 1)
770        self.assertEqual(len(alpha.reqs), 1)
771        self.assertEqual(len(alpha.reps), 1)
772        requestant = alpha.reqs.values()[0]
773        self.assertEqual(requestant.method, request['method'])
774        self.assertEqual(requestant.url, request['path'])
775        self.assertEqual(requestant.headers, {'accept': 'application/json',
776                                                'accept-encoding': 'identity',
777                                                'content-length': '0',
778                                                'host': 'localhost:6101'})
779
780
781        #timed out while stream still open so no responses in .responses
782        self.assertIs(beta.waited, True)
783        self.assertIs(beta.respondent.ended, False)
784        self.assertEqual(len(beta.responses), 0)
785        self.assertIn('content-type', beta.respondent.headers)
786        self.assertEqual(beta.respondent.headers['content-type'], 'text/event-stream')
787        self.assertIn('transfer-encoding', beta.respondent.headers)
788        self.assertEqual(beta.respondent.headers['transfer-encoding'], 'chunked')
789
790        self.assertTrue(len(beta.events) >= 3)
791        self.assertEqual(beta.respondent.retry, 1000)
792        self.assertTrue(int(beta.respondent.leid) >= 2)
793        event = beta.events.popleft()
794        self.assertEqual(event, {'id': '0', 'name': '', 'data': 'START'})
795        event = beta.events.popleft()
796        self.assertEqual(event, {'id': '1', 'name': '', 'data': '1'})
797        event = beta.events.popleft()
798        self.assertEqual(event, {'id': '2', 'name': '', 'data': '2'})
799        beta.events.clear()
800
801        #keep going until ended
802        timer.restart(duration=1.5)
803        while (not timer.expired):
804            alpha.serviceAll()
805            time.sleep(0.05)
806            beta.serviceAll()
807            time.sleep(0.05)
808            store.advanceStamp(0.1)
809
810        self.assertTrue(len(beta.events) >= 3)
811        self.assertEqual(beta.respondent.leid,  '9')
812        self.assertEqual(beta.events[-2], {'id': '9', 'name': '', 'data': '9'})
813        self.assertEqual(beta.events[-1], {'id': '9', 'name': '', 'data': 'END'})
814        beta.events.clear()
815
816        alpha.servant.closeAll()
817        beta.connector.close()
818
819        wireLogAlpha.close()
820        wireLogBeta.close()
821
822    def testValetServiceBasicSecure(self):
823        """
824        Test Valet WSGI service with secure TLS request response
825        """
826        console.terse("{0}\n".format(self.testValetServiceBasicSecure.__doc__))
827
828        store = storing.Store(stamp=0.0)
829
830        def wsgiApp(environ, start_response):
831            start_response('200 OK', [('Content-type','text/plain'),
832                                      ('Content-length', '12')])
833            return [b"Hello World!"]
834
835        console.terse("{0}\n".format("Building Valet ...\n"))
836        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
837        result = wireLogAlpha.reopen()
838
839        serverCertCommonName = 'localhost' # match hostname uses servers's cert commonname
840        #serverKeypath = '/etc/pki/tls/certs/server_key.pem'  # local server private key
841        #serverCertpath = '/etc/pki/tls/certs/server_cert.pem'  # local server public cert
842        #clientCafilepath = '/etc/pki/tls/certs/client.pem' # remote client public cert
843
844        serverKeypath = self.certdirpath + '/server_key.pem'  # local server private key
845        serverCertpath = self.certdirpath + '/server_cert.pem'  # local server public cert
846        clientCafilepath = self.certdirpath + '/client.pem' # remote client public cert
847
848        alpha = serving.Valet(port = 6101,
849                              bufsize=131072,
850                              wlog=wireLogAlpha,
851                              store=store,
852                              app=wsgiApp,
853                              scheme='https',
854                              keypath=serverKeypath,
855                              certpath=serverCertpath,
856                              cafilepath=clientCafilepath,)
857
858        self.assertIs(alpha.servant.reopen(), True)
859        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
860        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
861
862        console.terse("{0}\n".format("Building Patron ...\n"))
863        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
864        result = wireLogBeta.reopen()
865
866        #clientKeypath = '/etc/pki/tls/certs/client_key.pem'  # local client private key
867        #clientCertpath = '/etc/pki/tls/certs/client_cert.pem'  # local client public cert
868        #serverCafilepath = '/etc/pki/tls/certs/server.pem' # remote server public cert
869
870        clientKeypath = self.certdirpath + '/client_key.pem'  # local client private key
871        clientCertpath = self.certdirpath + '/client_cert.pem'  # local client public cert
872        serverCafilepath = self.certdirpath + '/server.pem' # remote server public cert
873
874        path = "https://{0}:{1}/".format('localhost', alpha.servant.eha[1])
875
876        beta = clienting.Patron(bufsize=131072,
877                                     wlog=wireLogBeta,
878                                     store=store,
879                                     path=path,
880                                     reconnectable=True,
881                                     scheme='https',
882                                     certedhost=serverCertCommonName,
883                                     keypath=clientKeypath,
884                                     certpath=clientCertpath,
885                                     cafilepath=serverCafilepath
886                                     )
887
888        self.assertIs(beta.connector.reopen(), True)
889        self.assertIs(beta.connector.accepted, False)
890        self.assertIs(beta.connector.connected, False)
891        self.assertIs(beta.connector.cutoff, False)
892
893        request = odict([('method', u'GET'),
894                         ('path', u'/echo?name=fame'),
895                         ('qargs', odict()),
896                         ('fragment', u''),
897                         ('headers', odict([('Accept', 'application/json'),
898                                            ('Content-Length', 0)])),
899                        ])
900
901        beta.requests.append(request)
902
903        while (beta.requests or beta.connector.txes or not beta.responses or
904               not alpha.idle()):
905            alpha.serviceAll()
906            time.sleep(0.05)
907            beta.serviceAll()
908            time.sleep(0.05)
909
910        self.assertIs(beta.connector.accepted, True)
911        self.assertIs(beta.connector.connected, True)
912        self.assertIs(beta.connector.cutoff, False)
913
914        self.assertEqual(len(alpha.servant.ixes), 1)
915        self.assertEqual(len(alpha.reqs), 1)
916        self.assertEqual(len(alpha.reps), 1)
917        requestant = alpha.reqs.values()[0]
918        self.assertEqual(requestant.method, request['method'])
919        self.assertEqual(requestant.url, request['path'])
920        self.assertEqual(requestant.headers, {'accept': 'application/json',
921                                                'accept-encoding': 'identity',
922                                                'content-length': '0',
923                                                'host': 'localhost:6101'})
924
925
926        self.assertEqual(len(beta.responses), 1)
927        response = beta.responses.popleft()
928        self.assertEqual(response['body'],bytearray(b'Hello World!'))
929        self.assertEqual(response['status'], 200)
930
931        responder = alpha.reps.values()[0]
932        self.assertTrue(responder.status.startswith, str(response['status']))
933        self.assertEqual(responder.headers, response['headers'])
934
935        alpha.servant.closeAll()
936        beta.connector.close()
937
938        wireLogAlpha.close()
939        wireLogBeta.close()
940
941    def testValetServiceBottleSecure(self):
942        """
943        Test Valet WSGI service secure TLS request response
944        """
945        console.terse("{0}\n".format(self.testValetServiceBottleSecure.__doc__))
946
947        try:
948            import bottle
949        except ImportError as ex:
950            console.terse("Bottle not available.\n")
951            return
952
953        store = storing.Store(stamp=0.0)
954
955        app = bottle.default_app() # create bottle app
956
957        @app.get('/echo')
958        @app.get('/echo/<action>')
959        @app.post('/echo')
960        @app.post('/echo/<action>')
961        def echoGet(action=None):
962            """
963            Echo back request data
964            """
965            query = dict(bottle.request.query.items())
966            body = bottle.request.json
967            raw = bottle.request.body.read()
968            form = odict(bottle.request.forms)
969
970            data = odict(verb=bottle.request.method,
971                        url=bottle.request.url,
972                        action=action,
973                        query=query,
974                        form=form,
975                        content=body)
976            return data
977
978
979        console.terse("{0}\n".format("Building Valet ...\n"))
980        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
981        result = wireLogAlpha.reopen()
982
983        serverCertCommonName = 'localhost' # match hostname uses servers's cert commonname
984        #serverKeypath = '/etc/pki/tls/certs/server_key.pem'  # local server private key
985        #serverCertpath = '/etc/pki/tls/certs/server_cert.pem'  # local server public cert
986        #clientCafilepath = '/etc/pki/tls/certs/client.pem' # remote client public cert
987
988        serverKeypath = self.certdirpath + '/server_key.pem'  # local server private key
989        serverCertpath = self.certdirpath + '/server_cert.pem'  # local server public cert
990        clientCafilepath = self.certdirpath + '/client.pem' # remote client public cert
991
992        alpha = serving.Valet(port = 6101,
993                              bufsize=131072,
994                              wlog=wireLogAlpha,
995                              store=store,
996                              app=app,
997                              scheme='https',
998                              keypath=serverKeypath,
999                              certpath=serverCertpath,
1000                              cafilepath=clientCafilepath,
1001                              )
1002        self.assertIs(alpha.servant.reopen(), True)
1003        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
1004        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
1005
1006        console.terse("{0}\n".format("Building Patron ...\n"))
1007        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
1008        result = wireLogBeta.reopen()
1009
1010        #clientKeypath = '/etc/pki/tls/certs/client_key.pem'  # local client private key
1011        #clientCertpath = '/etc/pki/tls/certs/client_cert.pem'  # local client public cert
1012        #serverCafilepath = '/etc/pki/tls/certs/server.pem' # remote server public cert
1013
1014        clientKeypath = self.certdirpath + '/client_key.pem'  # local client private key
1015        clientCertpath = self.certdirpath + '/client_cert.pem'  # local client public cert
1016        serverCafilepath = self.certdirpath + '/server.pem' # remote server public cert
1017
1018        path = "https://{0}:{1}/".format('localhost', alpha.servant.eha[1])
1019
1020        beta = clienting.Patron(bufsize=131072,
1021                                wlog=wireLogBeta,
1022                                store=store,
1023                                path=path,
1024                                reconnectable=True,
1025                                scheme='https',
1026                                certedhost=serverCertCommonName,
1027                                keypath=clientKeypath,
1028                                certpath=clientCertpath,
1029                                cafilepath=serverCafilepath
1030                                )
1031
1032        self.assertIs(beta.connector.reopen(), True)
1033        self.assertIs(beta.connector.accepted, False)
1034        self.assertIs(beta.connector.connected, False)
1035        self.assertIs(beta.connector.cutoff, False)
1036
1037        request = odict([('method', u'GET'),
1038                         ('path', u'/echo?name=fame'),
1039                         ('qargs', odict()),
1040                         ('fragment', u''),
1041                         ('headers', odict([('Accept', 'application/json'),
1042                                            ('Content-Length', 0)])),
1043                        ])
1044
1045        beta.requests.append(request)
1046        timer = StoreTimer(store, duration=1.0)
1047        while (beta.requests or beta.connector.txes or not beta.responses or
1048               not alpha.idle()):
1049            alpha.serviceAll()
1050            time.sleep(0.05)
1051            beta.serviceAll()
1052            time.sleep(0.05)
1053            store.advanceStamp(0.1)
1054
1055        self.assertIs(beta.connector.accepted, True)
1056        self.assertIs(beta.connector.connected, True)
1057        self.assertIs(beta.connector.cutoff, False)
1058
1059        self.assertEqual(len(alpha.servant.ixes), 1)
1060        self.assertEqual(len(alpha.reqs), 1)
1061        self.assertEqual(len(alpha.reps), 1)
1062        requestant = alpha.reqs.values()[0]
1063        self.assertEqual(requestant.method, request['method'])
1064        self.assertEqual(requestant.url, request['path'])
1065        self.assertEqual(requestant.headers, {'accept': 'application/json',
1066                                                'accept-encoding': 'identity',
1067                                                'content-length': '0',
1068                                                'host': 'localhost:6101'})
1069
1070        self.assertEqual(len(beta.responses), 1)
1071        response = beta.responses.popleft()
1072        self.assertEqual(response['status'], 200)
1073        self.assertEqual(response['reason'], 'OK')
1074        self.assertEqual(response['body'], bytearray(b'{"verb": "GET", "url": "https://localhost:6101/echo?name=fame", '
1075                                                    b'"action": null, "query": {"name": "fame"}, "form": {}, "content"'
1076                                                    b': null}'))
1077        self.assertEqual(response['data'],{'action': None,
1078                                            'content': None,
1079                                            'form': {},
1080                                            'query': {'name': 'fame'},
1081                                            'url': 'https://localhost:6101/echo?name=fame',
1082                                            'verb': 'GET'},)
1083
1084        responder = alpha.reps.values()[0]
1085        self.assertTrue(responder.status.startswith, str(response['status']))
1086        self.assertEqual(responder.headers, response['headers'])
1087
1088        alpha.servant.closeAll()
1089        beta.connector.close()
1090
1091        wireLogAlpha.close()
1092        wireLogBeta.close()
1093
1094    def testValetServiceBottleStreamSecure(self):
1095        """
1096        Test Valet WSGI service request response stream sse
1097        """
1098        console.terse("{0}\n".format(self.testValetServiceBottleStreamSecure.__doc__))
1099
1100        try:
1101            import bottle
1102        except ImportError as ex:
1103            console.terse("Bottle not available.\n")
1104            return
1105
1106        store = storing.Store(stamp=0.0)
1107
1108        app = bottle.default_app() # create bottle app
1109
1110        @app.get('/stream')
1111        def streamGet():
1112            """
1113            Create test server sent event stream that sends count events
1114            """
1115            timer = StoreTimer(store, duration=2.0)
1116            bottle.response.set_header('Content-Type',  'text/event-stream') #text
1117            bottle.response.set_header('Cache-Control',  'no-cache')
1118            # HTTP 1.1 servers detect text/event-stream and use Transfer-Encoding: chunked
1119            # Set client-side auto-reconnect timeout, ms.
1120            yield 'retry: 1000\n\n'
1121            i = 0
1122            yield 'id: {0}\n'.format(i)
1123            i += 1
1124            yield 'data: START\n\n'
1125            n = 1
1126            while not timer.expired:
1127                yield 'id: {0}\n'.format(i)
1128                i += 1
1129                yield 'data: {0}\n\n'.format(n)
1130                n += 1
1131            yield "data: END\n\n"
1132
1133        console.terse("{0}\n".format("Building Valet ...\n"))
1134        wireLogAlpha = wiring.WireLog(buffify=True, same=True)
1135        result = wireLogAlpha.reopen()
1136
1137        serverCertCommonName = 'localhost' # match hostname uses servers's cert commonname
1138        #serverKeypath = '/etc/pki/tls/certs/server_key.pem'  # local server private key
1139        #serverCertpath = '/etc/pki/tls/certs/server_cert.pem'  # local server public cert
1140        #clientCafilepath = '/etc/pki/tls/certs/client.pem' # remote client public cert
1141
1142        serverKeypath = self.certdirpath + '/server_key.pem'  # local server private key
1143        serverCertpath = self.certdirpath + '/server_cert.pem'  # local server public cert
1144        clientCafilepath = self.certdirpath + '/client.pem' # remote client public cert
1145
1146        alpha = serving.Valet(port = 6101,
1147                              bufsize=131072,
1148                              wlog=wireLogAlpha,
1149                              store=store,
1150                              app=app,
1151                              scheme='https',
1152                              keypath=serverKeypath,
1153                              certpath=serverCertpath,
1154                              cafilepath=clientCafilepath,
1155                              )
1156        self.assertIs(alpha.servant.reopen(), True)
1157        self.assertEqual(alpha.servant.ha, ('0.0.0.0', 6101))
1158        self.assertEqual(alpha.servant.eha, ('127.0.0.1', 6101))
1159
1160        console.terse("{0}\n".format("Building Patron ...\n"))
1161        wireLogBeta = wiring.WireLog(buffify=True,  same=True)
1162        result = wireLogBeta.reopen()
1163
1164        #clientKeypath = '/etc/pki/tls/certs/client_key.pem'  # local client private key
1165        #clientCertpath = '/etc/pki/tls/certs/client_cert.pem'  # local client public cert
1166        #serverCafilepath = '/etc/pki/tls/certs/server.pem' # remote server public cert
1167
1168        clientKeypath = self.certdirpath + '/client_key.pem'  # local client private key
1169        clientCertpath = self.certdirpath + '/client_cert.pem'  # local client public cert
1170        serverCafilepath = self.certdirpath + '/server.pem' # remote server public cert
1171
1172        path = "https://{0}:{1}/".format('localhost', alpha.servant.eha[1])
1173
1174        beta = clienting.Patron(bufsize=131072,
1175                                     wlog=wireLogBeta,
1176                                     store=store,
1177                                     path=path,
1178                                     reconnectable=True,
1179                                     scheme='https',
1180                                     certedhost=serverCertCommonName,
1181                                     keypath=clientKeypath,
1182                                     certpath=clientCertpath,
1183                                     cafilepath=serverCafilepath
1184                                     )
1185
1186        self.assertIs(beta.connector.reopen(), True)
1187        self.assertIs(beta.connector.accepted, False)
1188        self.assertIs(beta.connector.connected, False)
1189        self.assertIs(beta.connector.cutoff, False)
1190
1191        request = odict([('method', u'GET'),
1192                         ('path', u'/stream'),
1193                         ('qargs', odict()),
1194                         ('fragment', u''),
1195                         ('headers', odict([('Accept', 'application/json'),
1196                                            ('Content-Length', 0)])),
1197                         ('body', None),
1198                        ])
1199
1200        beta.requests.append(request)
1201        timer = StoreTimer(store, duration=1.0)
1202        while (not timer.expired):
1203            alpha.serviceAll()
1204            time.sleep(0.05)
1205            beta.serviceAll()
1206            time.sleep(0.05)
1207            store.advanceStamp(0.1)
1208
1209        self.assertIs(beta.connector.accepted, True)
1210        self.assertIs(beta.connector.connected, True)
1211        self.assertIs(beta.connector.cutoff, False)
1212
1213        self.assertEqual(len(alpha.servant.ixes), 1)
1214        self.assertEqual(len(alpha.reqs), 1)
1215        self.assertEqual(len(alpha.reps), 1)
1216        requestant = alpha.reqs.values()[0]
1217        self.assertEqual(requestant.method, request['method'])
1218        self.assertEqual(requestant.url, request['path'])
1219        self.assertEqual(requestant.headers, {'accept': 'application/json',
1220                                                'accept-encoding': 'identity',
1221                                                'content-length': '0',
1222                                                'host': 'localhost:6101'})
1223
1224
1225        #timed out while stream still open so no responses in .responses
1226        self.assertIs(beta.waited, True)
1227        self.assertIs(beta.respondent.ended, False)
1228        self.assertEqual(len(beta.responses), 0)
1229        self.assertIn('content-type', beta.respondent.headers)
1230        self.assertEqual(beta.respondent.headers['content-type'], 'text/event-stream')
1231        self.assertIn('transfer-encoding', beta.respondent.headers)
1232        self.assertEqual(beta.respondent.headers['transfer-encoding'], 'chunked')
1233
1234        self.assertTrue(len(beta.events) >= 3)
1235        self.assertEqual(beta.respondent.retry, 1000)
1236        self.assertTrue(int(beta.respondent.leid) >= 2)
1237        event = beta.events.popleft()
1238        self.assertEqual(event, {'id': '0', 'name': '', 'data': 'START'})
1239        event = beta.events.popleft()
1240        self.assertEqual(event, {'id': '1', 'name': '', 'data': '1'})
1241        event = beta.events.popleft()
1242        self.assertEqual(event, {'id': '2', 'name': '', 'data': '2'})
1243        beta.events.clear()
1244
1245        #keep going until ended
1246        timer.restart(duration=1.5)
1247        while (not timer.expired):
1248            alpha.serviceAll()
1249            time.sleep(0.05)
1250            beta.serviceAll()
1251            time.sleep(0.05)
1252            store.advanceStamp(0.1)
1253
1254        self.assertTrue(len(beta.events) >= 3)
1255        self.assertEqual(beta.respondent.leid,  '9')
1256        self.assertEqual(beta.events[-2], {'id': '9', 'name': '', 'data': '9'})
1257        self.assertEqual(beta.events[-1], {'id': '9', 'name': '', 'data': 'END'})
1258        beta.events.clear()
1259
1260        alpha.servant.closeAll()
1261        beta.connector.close()
1262
1263        wireLogAlpha.close()
1264        wireLogBeta.close()
1265
1266
1267def runOne(test):
1268    '''
1269    Unittest Runner
1270    '''
1271    test = BasicTestCase(test)
1272    suite = unittest.TestSuite([test])
1273    unittest.TextTestRunner(verbosity=2).run(suite)
1274
1275def runSome():
1276    """ Unittest runner """
1277    tests =  []
1278    names = [
1279             'testHttpError',
1280             'testPorterServiceEcho',
1281             'testValetServiceBasic',
1282             'testValetServiceBottle',
1283             'testValetServiceBottleNoContentLength',
1284             'testValetServiceBottleNonPersistent',
1285             'testValetServiceBottleStream',
1286             'testValetServiceBasicSecure',
1287             'testValetServiceBottleSecure',
1288             'testValetServiceBottleStreamSecure',
1289            ]
1290    tests.extend(map(BasicTestCase, names))
1291    suite = unittest.TestSuite(tests)
1292    unittest.TextTestRunner(verbosity=2).run(suite)
1293
1294def runAll():
1295    """ Unittest runner """
1296    suite = unittest.TestSuite()
1297    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BasicTestCase))
1298    unittest.TextTestRunner(verbosity=2).run(suite)
1299
1300if __name__ == '__main__' and __package__ is None:
1301
1302    #
1303
1304    #runAll() #run all unittests
1305
1306    runSome()#only run some
1307
1308    #runOne('testValetServiceBottle')
1309    #runOne('testValetServiceBottleStreamSecure')
1310    #runOne('testHttpError')
1311    #runOne('testValetServiceBottleStream')
1312