Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

protocol.py 164KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. import binascii
  27. import hashlib
  28. import base64
  29. import struct
  30. import random
  31. import os
  32. import pickle
  33. import copy
  34. import json
  35. import time
  36. from typing import Optional, Union, Tuple, Dict
  37. from pprint import pformat
  38. from collections import deque
  39. from urllib import parse
  40. from autobahn import __version__
  41. from autobahn.websocket.interfaces import IWebSocketChannel, \
  42. IWebSocketChannelFrameApi, \
  43. IWebSocketChannelStreamingApi
  44. from autobahn.websocket.types import ConnectingRequest, ConnectionRequest, ConnectionResponse, ConnectionDeny
  45. from autobahn.wamp.types import TransportDetails
  46. from autobahn.util import Stopwatch, wildcards2patterns, encode_truncate, hltype, hlval
  47. from autobahn.util import _LazyHexFormatter
  48. from autobahn.util import ObservableMixin
  49. from autobahn.websocket.utf8validator import Utf8Validator
  50. from autobahn.websocket.xormasker import XorMaskerNull, create_xor_masker
  51. from autobahn.websocket.compress import PERMESSAGE_COMPRESSION_EXTENSION
  52. from autobahn.websocket.util import parse_url
  53. from autobahn.exception import PayloadExceededError, Disconnected
  54. from autobahn.util import _maybe_tls_reason
  55. import txaio
  56. import hyperlink
  57. # https://stackoverflow.com/a/15844751/884770
  58. # from types import NoneType
  59. NoneType = type(None)
  60. __all__ = ("WebSocketProtocol",
  61. "WebSocketFactory",
  62. "WebSocketServerProtocol",
  63. "WebSocketServerFactory",
  64. "WebSocketClientProtocol",
  65. "WebSocketClientFactory")
  66. def _url_to_origin(url):
  67. """
  68. Given an RFC6455 Origin URL, this returns the (scheme, host, port)
  69. triple. If there's no port, and the scheme isn't http or https
  70. then port will be None
  71. """
  72. if url.lower() == 'null':
  73. return 'null'
  74. res = parse.urlsplit(url)
  75. scheme = res.scheme.lower()
  76. if scheme == 'file':
  77. # when browsing local files, Chrome sends file:// URLs,
  78. # Firefox sends 'null'
  79. return 'null'
  80. host = res.hostname
  81. port = res.port
  82. if port is None:
  83. try:
  84. port = {'https': 443, 'http': 80}[scheme]
  85. except KeyError:
  86. port = None
  87. if not host:
  88. raise ValueError("No host part in Origin '{}'".format(url))
  89. return scheme, host, port
  90. def _is_same_origin(websocket_origin, host_scheme, host_port, host_policy):
  91. """
  92. Internal helper. Returns True if the provided websocket_origin
  93. triple should be considered valid given the provided policy and
  94. expected host_port.
  95. Currently, the policy is just the list of allowedOriginsPatterns
  96. from the WebSocketProtocol instance. Schemes and ports are matched
  97. first, and only if there is not a mismatch do we compare each
  98. allowed-origin pattern against the host.
  99. """
  100. if websocket_origin == 'null':
  101. # nothing is the same as the null origin
  102. return False
  103. if not isinstance(websocket_origin, tuple) or not len(websocket_origin) == 3:
  104. raise ValueError("'websocket_origin' must be a 3-tuple")
  105. (origin_scheme, origin_host, origin_port) = websocket_origin
  106. # so, theoretically we should match on the 3-tuple of (scheme,
  107. # origin, port) to follow the RFC. However, the existing API just
  108. # allows you to pass a list of regular expressions that match
  109. # against the Origin header -- so to keep that API working, we
  110. # just match a reconstituted/sanitized Origin line against the
  111. # regular expressions. We *do* explicitly match the scheme first,
  112. # however!
  113. # therefore, the default of "*" will still match everything (even
  114. # if things are on weird ports). To be "actually secure" and pass
  115. # explicit ports, you can put it in your matcher
  116. # (e.g. "https://*.example.com:1234")
  117. template = '{scheme}://{host}:{port}'
  118. origin_header = template.format(
  119. scheme=origin_scheme,
  120. host=origin_host,
  121. port=origin_port,
  122. )
  123. # so, this will be matching against e.g. "http://example.com:8080"
  124. for origin_pattern in host_policy:
  125. if origin_pattern.match(origin_header):
  126. return True
  127. return False
  128. class TrafficStats(object):
  129. def __init__(self):
  130. self.reset()
  131. def reset(self):
  132. # all of the following only tracks data messages, not control frames, not handshaking
  133. #
  134. self.outgoingOctetsWireLevel = 0
  135. self.outgoingOctetsWebSocketLevel = 0
  136. self.outgoingOctetsAppLevel = 0
  137. self.outgoingWebSocketFrames = 0
  138. self.outgoingWebSocketMessages = 0
  139. self.incomingOctetsWireLevel = 0
  140. self.incomingOctetsWebSocketLevel = 0
  141. self.incomingOctetsAppLevel = 0
  142. self.incomingWebSocketFrames = 0
  143. self.incomingWebSocketMessages = 0
  144. # the following track any traffic before the WebSocket connection
  145. # reaches STATE_OPEN (this includes WebSocket opening handshake
  146. # proxy handling and such)
  147. self.preopenOutgoingOctetsWireLevel = 0
  148. self.preopenIncomingOctetsWireLevel = 0
  149. def __json__(self):
  150. # compression ratio = compressed size / uncompressed size
  151. #
  152. if self.outgoingOctetsAppLevel > 0:
  153. outgoingCompressionRatio = float(self.outgoingOctetsWebSocketLevel) / float(self.outgoingOctetsAppLevel)
  154. else:
  155. outgoingCompressionRatio = None
  156. if self.incomingOctetsAppLevel > 0:
  157. incomingCompressionRatio = float(self.incomingOctetsWebSocketLevel) / float(self.incomingOctetsAppLevel)
  158. else:
  159. incomingCompressionRatio = None
  160. # protocol overhead = non-payload size / payload size
  161. #
  162. if self.outgoingOctetsWebSocketLevel > 0:
  163. outgoingWebSocketOverhead = float(self.outgoingOctetsWireLevel - self.outgoingOctetsWebSocketLevel) / float(self.outgoingOctetsWebSocketLevel)
  164. else:
  165. outgoingWebSocketOverhead = None
  166. if self.incomingOctetsWebSocketLevel > 0:
  167. incomingWebSocketOverhead = float(self.incomingOctetsWireLevel - self.incomingOctetsWebSocketLevel) / float(self.incomingOctetsWebSocketLevel)
  168. else:
  169. incomingWebSocketOverhead = None
  170. return {'outgoingOctetsWireLevel': self.outgoingOctetsWireLevel,
  171. 'outgoingOctetsWebSocketLevel': self.outgoingOctetsWebSocketLevel,
  172. 'outgoingOctetsAppLevel': self.outgoingOctetsAppLevel,
  173. 'outgoingCompressionRatio': outgoingCompressionRatio,
  174. 'outgoingWebSocketOverhead': outgoingWebSocketOverhead,
  175. 'outgoingWebSocketFrames': self.outgoingWebSocketFrames,
  176. 'outgoingWebSocketMessages': self.outgoingWebSocketMessages,
  177. 'preopenOutgoingOctetsWireLevel': self.preopenOutgoingOctetsWireLevel,
  178. 'incomingOctetsWireLevel': self.incomingOctetsWireLevel,
  179. 'incomingOctetsWebSocketLevel': self.incomingOctetsWebSocketLevel,
  180. 'incomingOctetsAppLevel': self.incomingOctetsAppLevel,
  181. 'incomingCompressionRatio': incomingCompressionRatio,
  182. 'incomingWebSocketOverhead': incomingWebSocketOverhead,
  183. 'incomingWebSocketFrames': self.incomingWebSocketFrames,
  184. 'incomingWebSocketMessages': self.incomingWebSocketMessages,
  185. 'preopenIncomingOctetsWireLevel': self.preopenIncomingOctetsWireLevel}
  186. def __str__(self):
  187. return json.dumps(self.__json__())
  188. class FrameHeader(object):
  189. """
  190. Thin-wrapper for storing WebSocket frame metadata.
  191. FOR INTERNAL USE ONLY!
  192. """
  193. def __init__(self, opcode, fin, rsv, length, mask):
  194. """
  195. Constructor.
  196. :param opcode: Frame opcode (0-15).
  197. :type opcode: int
  198. :param fin: Frame FIN flag.
  199. :type fin: bool
  200. :param rsv: Frame reserved flags (0-7).
  201. :type rsv: int
  202. :param length: Frame payload length.
  203. :type length: int
  204. :param mask: Frame mask (binary string) or None.
  205. :type mask: str
  206. """
  207. self.opcode = opcode
  208. self.fin = fin
  209. self.rsv = rsv
  210. self.length = length
  211. self.mask = mask
  212. def parseHttpHeader(data):
  213. """
  214. Parses the beginning of a HTTP request header (the data up to the \n\n line) into a pair
  215. of status line and HTTP headers dictionary.
  216. Header keys are normalized to all-lower-case.
  217. FOR INTERNAL USE ONLY!
  218. :param data: The HTTP header data up to the \n\n line.
  219. :type data: bytes
  220. :returns: Tuple of HTTP status line, headers and headers count.
  221. """
  222. # By default, message header field parameters in Hypertext Transfer
  223. # Protocol (HTTP) messages cannot carry characters outside the ISO-
  224. # 8859-1 character set.
  225. #
  226. # See:
  227. # - http://tools.ietf.org/html/rfc5987
  228. # - https://github.com/crossbario/autobahn-python/issues/533
  229. #
  230. raw = data.decode('iso-8859-1').splitlines()
  231. http_status_line = raw[0].strip()
  232. http_headers = {}
  233. http_headers_cnt = {}
  234. for h in raw[1:]:
  235. i = h.find(":")
  236. if i > 0:
  237. # HTTP header keys are case-insensitive
  238. key = h[:i].strip().lower()
  239. value = h[i + 1:].strip()
  240. # handle HTTP headers split across multiple lines
  241. if key in http_headers:
  242. http_headers[key] += ', {}'.format(value)
  243. http_headers_cnt[key] += 1
  244. else:
  245. http_headers[key] = value
  246. http_headers_cnt[key] = 1
  247. else:
  248. # skip bad HTTP header
  249. pass
  250. return http_status_line, http_headers, http_headers_cnt
  251. class Timings(object):
  252. """
  253. Helper class to track timings by key. This class also supports item access,
  254. iteration and conversion to string.
  255. """
  256. def __init__(self):
  257. self._stopwatch = Stopwatch()
  258. self._timings = {}
  259. def track(self, key):
  260. """
  261. Track elapsed for key.
  262. :param key: Key under which to track the timing.
  263. :type key: str
  264. """
  265. self._timings[key] = self._stopwatch.elapsed()
  266. def diff(self, startKey, endKey, formatted=True):
  267. """
  268. Get elapsed difference between two previously tracked keys.
  269. :param startKey: First key for interval (older timestamp).
  270. :type startKey: str
  271. :param endKey: Second key for interval (younger timestamp).
  272. :type endKey: str
  273. :param formatted: If ``True``, format computed time period and return string.
  274. :type formatted: bool
  275. :returns: Computed time period in seconds (or formatted string).
  276. """
  277. if endKey in self._timings and startKey in self._timings:
  278. d = self._timings[endKey] - self._timings[startKey]
  279. if formatted:
  280. if d < 0.00001: # 10us
  281. s = "{} ns".format(round(d * 1000000000.))
  282. elif d < 0.01: # 10ms
  283. s = "{} us".format(round(d * 1000000.))
  284. elif d < 10: # 10s
  285. s = "{} ms".format(round(d * 1000.))
  286. else:
  287. s = "{} s".format(round(d))
  288. return s.rjust(8)
  289. else:
  290. return d
  291. else:
  292. if formatted:
  293. return "n.a.".rjust(8)
  294. else:
  295. return None
  296. def __getitem__(self, key):
  297. return self._timings.get(key, None)
  298. def __iter__(self):
  299. return self._timings.__iter__()
  300. def __str__(self):
  301. return pformat(self._timings)
  302. class WebSocketProtocol(ObservableMixin):
  303. """
  304. Protocol base class for WebSocket.
  305. This class implements:
  306. * :class:`autobahn.websocket.interfaces.IWebSocketChannel`
  307. * :class:`autobahn.websocket.interfaces.IWebSocketChannelFrameApi`
  308. * :class:`autobahn.websocket.interfaces.IWebSocketChannelStreamingApi`
  309. """
  310. # set in WebSocketAdapterProtocol.connectionMade (Twisted) and FIXME (asyncio)
  311. peer = '<never connected>'
  312. SUPPORTED_SPEC_VERSIONS = [10, 11, 12, 13, 14, 15, 16, 17, 18]
  313. """
  314. WebSocket protocol spec (draft) versions supported by this implementation.
  315. Use of version 18 indicates RFC6455. Use of versions < 18 indicate actual
  316. draft spec versions (Hybi-Drafts).
  317. """
  318. SUPPORTED_PROTOCOL_VERSIONS = [8, 13]
  319. """
  320. WebSocket protocol versions supported by this implementation.
  321. """
  322. SPEC_TO_PROTOCOL_VERSION = {10: 8, 11: 8, 12: 8, 13: 13, 14: 13, 15: 13, 16: 13, 17: 13, 18: 13}
  323. """
  324. Mapping from protocol spec (draft) version to protocol version.
  325. """
  326. PROTOCOL_TO_SPEC_VERSION = {8: 12, 13: 18}
  327. """
  328. Mapping from protocol version to the latest protocol spec (draft) version
  329. using that protocol version.
  330. """
  331. DEFAULT_SPEC_VERSION = 18
  332. """
  333. Default WebSocket protocol spec version this implementation speaks: final
  334. RFC6455.
  335. """
  336. _WS_MAGIC = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  337. """
  338. Protocol defined magic used during WebSocket handshake (used in Hybi-drafts
  339. and final RFC6455.
  340. """
  341. _QUEUED_WRITE_DELAY = 0.00001
  342. """
  343. For synched/chopped writes, this is the reactor reentry delay in seconds.
  344. """
  345. MESSAGE_TYPE_TEXT = 1
  346. """
  347. WebSocket text message type (UTF-8 payload).
  348. """
  349. MESSAGE_TYPE_BINARY = 2
  350. """
  351. WebSocket binary message type (arbitrary binary payload).
  352. """
  353. # WebSocket protocol state:
  354. # (STATE_PROXY_CONNECTING) => STATE_CONNECTING => STATE_OPEN => STATE_CLOSING => STATE_CLOSED
  355. #
  356. STATE_CLOSED = 0
  357. STATE_CONNECTING = 1
  358. STATE_CLOSING = 2
  359. STATE_OPEN = 3
  360. STATE_PROXY_CONNECTING = 4
  361. # Streaming Send State
  362. SEND_STATE_GROUND = 0
  363. SEND_STATE_MESSAGE_BEGIN = 1
  364. SEND_STATE_INSIDE_MESSAGE = 2
  365. SEND_STATE_INSIDE_MESSAGE_FRAME = 3
  366. # WebSocket protocol close codes
  367. # See:https://www.iana.org/assignments/websocket/websocket.xml#close-code-number-rules
  368. #
  369. CLOSE_STATUS_CODE_NORMAL = 1000
  370. """Normal close of connection."""
  371. CLOSE_STATUS_CODE_GOING_AWAY = 1001
  372. """Going away."""
  373. CLOSE_STATUS_CODE_PROTOCOL_ERROR = 1002
  374. """Protocol error."""
  375. CLOSE_STATUS_CODE_UNSUPPORTED_DATA = 1003
  376. """Unsupported data."""
  377. CLOSE_STATUS_CODE_RESERVED1 = 1004
  378. """RESERVED"""
  379. CLOSE_STATUS_CODE_NULL = 1005 # MUST NOT be set in close frame!
  380. """No status received. (MUST NOT be used as status code when sending a close)."""
  381. CLOSE_STATUS_CODE_ABNORMAL_CLOSE = 1006 # MUST NOT be set in close frame!
  382. """Abnormal close of connection. (MUST NOT be used as status code when sending a close)."""
  383. CLOSE_STATUS_CODE_INVALID_PAYLOAD = 1007
  384. """Invalid frame payload data."""
  385. CLOSE_STATUS_CODE_POLICY_VIOLATION = 1008
  386. """Policy violation."""
  387. CLOSE_STATUS_CODE_MESSAGE_TOO_BIG = 1009
  388. """Message too big."""
  389. CLOSE_STATUS_CODE_MANDATORY_EXTENSION = 1010
  390. """Mandatory extension."""
  391. CLOSE_STATUS_CODE_INTERNAL_ERROR = 1011
  392. """The peer encountered an unexpected condition or internal error."""
  393. CLOSE_STATUS_CODE_SERVICE_RESTART = 1012
  394. """Service restart."""
  395. CLOSE_STATUS_CODE_TRY_AGAIN_LATER = 1013
  396. """Try again later."""
  397. CLOSE_STATUS_CODE_UNASSIGNED1 = 1014
  398. """Unassiged."""
  399. CLOSE_STATUS_CODE_TLS_HANDSHAKE_FAILED = 1015 # MUST NOT be set in close frame!
  400. """TLS handshake failed, i.e. server certificate could not be verified. (MUST NOT be used as status code when sending a close)."""
  401. CLOSE_STATUS_CODES_ALLOWED = [CLOSE_STATUS_CODE_NORMAL,
  402. CLOSE_STATUS_CODE_GOING_AWAY,
  403. CLOSE_STATUS_CODE_PROTOCOL_ERROR,
  404. CLOSE_STATUS_CODE_UNSUPPORTED_DATA,
  405. CLOSE_STATUS_CODE_INVALID_PAYLOAD,
  406. CLOSE_STATUS_CODE_POLICY_VIOLATION,
  407. CLOSE_STATUS_CODE_MESSAGE_TOO_BIG,
  408. CLOSE_STATUS_CODE_MANDATORY_EXTENSION,
  409. CLOSE_STATUS_CODE_INTERNAL_ERROR,
  410. CLOSE_STATUS_CODE_SERVICE_RESTART,
  411. CLOSE_STATUS_CODE_TRY_AGAIN_LATER]
  412. """Status codes allowed to send in close."""
  413. CONFIG_ATTRS_COMMON = ['logOctets',
  414. 'logFrames',
  415. 'trackTimings',
  416. 'utf8validateIncoming',
  417. 'applyMask',
  418. 'maxFramePayloadSize',
  419. 'maxMessagePayloadSize',
  420. 'autoFragmentSize',
  421. 'failByDrop',
  422. 'echoCloseCodeReason',
  423. 'openHandshakeTimeout',
  424. 'closeHandshakeTimeout',
  425. 'tcpNoDelay',
  426. 'autoPingInterval',
  427. 'autoPingTimeout',
  428. 'autoPingSize',
  429. 'autoPingRestartOnAnyTraffic']
  430. """
  431. Configuration attributes common to servers and clients.
  432. """
  433. CONFIG_ATTRS_SERVER = ['versions',
  434. 'webStatus',
  435. 'requireMaskedClientFrames',
  436. 'maskServerFrames',
  437. 'perMessageCompressionAccept',
  438. 'serveFlashSocketPolicy',
  439. 'flashSocketPolicy',
  440. 'allowedOrigins',
  441. 'allowedOriginsPatterns',
  442. 'allowNullOrigin',
  443. 'maxConnections',
  444. 'trustXForwardedFor']
  445. """
  446. Configuration attributes specific to servers.
  447. """
  448. CONFIG_ATTRS_CLIENT = ['version',
  449. 'acceptMaskedServerFrames',
  450. 'maskClientFrames',
  451. 'serverConnectionDropTimeout',
  452. 'perMessageCompressionOffers',
  453. 'perMessageCompressionAccept']
  454. """
  455. Configuration attributes specific to clients.
  456. """
  457. def __init__(self):
  458. #: a Future/Deferred that fires when we hit STATE_CLOSED
  459. self.is_closed = txaio.create_future()
  460. self.is_open = txaio.create_future()
  461. # XXX should we have open/close here too, or do you HAVE to use is_closed future?
  462. # XXX what about when_open() and when_closed() as well/instead?
  463. self.set_valid_events([
  464. "message", # like onMessage (takes: payload, is_binary=)
  465. ])
  466. # set in
  467. # * autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionMade
  468. # * autobahn.asyncio.websocket.WebSocketAdapterProtocol.
  469. self._transport_details: Optional[TransportDetails] = TransportDetails()
  470. @property
  471. def transport_details(self) -> Optional[TransportDetails]:
  472. """
  473. Implements :class:`autobahn.wamp.interfaces.ITransport.transport_details`.
  474. """
  475. return self._transport_details
  476. def onOpen(self):
  477. """
  478. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
  479. """
  480. self.log.debug("WebSocketProtocol.onOpen")
  481. def onMessageBegin(self, isBinary):
  482. """
  483. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageBegin`
  484. """
  485. self.message_is_binary = isBinary
  486. self.message_data = []
  487. self.message_data_total_length = 0
  488. def onMessageFrameBegin(self, length):
  489. """
  490. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameBegin`
  491. """
  492. self.frame_length = length
  493. self.frame_data = []
  494. self.message_data_total_length += length
  495. if not self.failedByMe:
  496. if 0 < self.maxMessagePayloadSize < self.message_data_total_length:
  497. self.wasMaxMessagePayloadSizeExceeded = True
  498. self._max_message_size_exceeded(self.message_data_total_length,
  499. self.maxMessagePayloadSize,
  500. 'received WebSocket message size {} exceeds payload limit of {} octets'.format(self.message_data_total_length, self.maxMessagePayloadSize))
  501. elif 0 < self.maxFramePayloadSize < length:
  502. self.wasMaxFramePayloadSizeExceeded = True
  503. self._max_message_size_exceeded(length,
  504. self.maxFramePayloadSize,
  505. 'received WebSocket frame size {} exceeds payload limit of {} octets'.format(length, self.maxFramePayloadSize))
  506. def onMessageFrameData(self, payload):
  507. """
  508. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameData`
  509. """
  510. if not self.failedByMe:
  511. if self.websocket_version == 0:
  512. self.message_data_total_length += len(payload)
  513. if 0 < self.maxMessagePayloadSize < self.message_data_total_length:
  514. self.wasMaxMessagePayloadSizeExceeded = True
  515. self._max_message_size_exceeded(self.message_data_total_length,
  516. self.maxMessagePayloadSize,
  517. 'received (partial) WebSocket message size {} (already) exceeds payload limit of {} octets'.format(self.message_data_total_length, self.maxMessagePayloadSize))
  518. self.message_data.append(payload)
  519. else:
  520. self.frame_data.append(payload)
  521. def onMessageFrameEnd(self):
  522. """
  523. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameEnd`
  524. """
  525. if not self.failedByMe:
  526. self._onMessageFrame(self.frame_data)
  527. self.frame_data = None
  528. def onMessageFrame(self, payload):
  529. """
  530. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrame`
  531. """
  532. if not self.failedByMe:
  533. self.message_data.extend(payload)
  534. def onMessageEnd(self):
  535. """
  536. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageEnd`
  537. """
  538. if not self.failedByMe:
  539. payload = b''.join(self.message_data)
  540. if self.trackedTimings:
  541. self.trackedTimings.track("onMessage")
  542. self._onMessage(payload, self.message_is_binary)
  543. # notify any listeners about this message
  544. f = self.fire("message", payload, is_binary=self.message_is_binary)
  545. def error(f):
  546. self.log.error(
  547. "Firing signal 'message' failed: {fail}",
  548. fail=f,
  549. )
  550. # all we can really do here is log; user code error
  551. txaio.add_callbacks(f, None, error)
  552. self.message_data = None
  553. def onMessage(self, payload, isBinary):
  554. """
  555. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
  556. """
  557. self.log.debug(
  558. "WebSocketProtocol.onMessage(payload=<{payload_len} bytes)>, isBinary={isBinary}",
  559. payload_len=(len(payload) if payload else 0),
  560. isBinary=isBinary,
  561. )
  562. def onPing(self, payload):
  563. """
  564. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onPing`
  565. """
  566. self.log.debug(
  567. "WebSocketProtocol.onPing(payload=<{payload_len} bytes>)",
  568. payload_len=(len(payload) if payload else 0),
  569. )
  570. if self.state == WebSocketProtocol.STATE_OPEN:
  571. self.sendPong(payload)
  572. def onPong(self, payload):
  573. """
  574. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onPong`
  575. """
  576. self.log.debug(
  577. "WebSocketProtocol.onPong(payload=<{payload_len} bytes>)",
  578. payload_len=(len(payload) if payload else 0),
  579. )
  580. def onClose(self, wasClean, code, reason):
  581. """
  582. Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onClose`
  583. """
  584. self.log.debug(
  585. "WebSocketProtocol.onClose(wasClean={wasClean}, code={code}, reason={reason})",
  586. wasClean=wasClean,
  587. code=code,
  588. reason=reason,
  589. )
  590. def onCloseFrame(self, code, reasonRaw):
  591. """
  592. Callback when a Close frame was received. The default implementation answers by
  593. sending a Close when no Close was sent before. Otherwise it drops
  594. the TCP connection either immediately (when we are a server) or after a timeout
  595. (when we are a client and expect the server to drop the TCP).
  596. :param code: Close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).
  597. :type code: int
  598. :param reasonRaw: Close reason (when present, a status code MUST have been also be present).
  599. :type reasonRaw: bytes
  600. """
  601. self.remoteCloseCode = None
  602. self.remoteCloseReason = None
  603. # reserved close codes: 0-999, 1004, 1005, 1006, 1011-2999, >= 5000
  604. #
  605. if code is not None and (code < 1000 or (1000 <= code <= 2999 and code not in WebSocketProtocol.CLOSE_STATUS_CODES_ALLOWED) or code >= 5000):
  606. if self._protocol_violation('invalid close code {}'.format(code)):
  607. return True
  608. else:
  609. self.remoteCloseCode = WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL
  610. else:
  611. self.remoteCloseCode = code
  612. # closing reason
  613. #
  614. if reasonRaw is not None:
  615. # we use our own UTF-8 validator to get consistent and fully conformant
  616. # UTF-8 validation behavior
  617. u = Utf8Validator()
  618. val = u.validate(reasonRaw)
  619. # the UTF8 must be valid _and_ end on a Unicode code point
  620. if not (val[0] and val[1]):
  621. if self._invalid_payload('invalid close reason (non-UTF8 payload)'):
  622. return True
  623. else:
  624. self.remoteCloseReason = reasonRaw.decode('utf8')
  625. # handle receive of close frame depending on protocol state
  626. #
  627. if self.state == WebSocketProtocol.STATE_CLOSING:
  628. # We already initiated the closing handshake, so this
  629. # is the peer's reply to our close frame.
  630. # cancel any closing HS timer if present
  631. #
  632. if self.closeHandshakeTimeoutCall is not None:
  633. self.log.debug("connection closed properly: canceling closing handshake timeout")
  634. self.closeHandshakeTimeoutCall.cancel()
  635. self.closeHandshakeTimeoutCall = None
  636. self.wasClean = True
  637. if self.factory.isServer:
  638. # When we are a server, we immediately drop the TCP.
  639. self.dropConnection(abort=True)
  640. else:
  641. # When we are a client, the server should drop the TCP
  642. # If that doesn't happen, we do. And that will set wasClean = False.
  643. if self.serverConnectionDropTimeout > 0:
  644. self.serverConnectionDropTimeoutCall = txaio.call_later(
  645. self.serverConnectionDropTimeout,
  646. self.onServerConnectionDropTimeout,
  647. )
  648. elif self.state == WebSocketProtocol.STATE_OPEN:
  649. # The peer initiates a closing handshake, so we reply
  650. # by sending close frame.
  651. self.wasClean = True
  652. if self.websocket_version == 0:
  653. self.sendCloseFrame(isReply=True)
  654. else:
  655. # Either reply with same code/reason, or code == NORMAL/reason=None
  656. if self.echoCloseCodeReason:
  657. self.sendCloseFrame(code=self.remoteCloseCode, reasonUtf8=encode_truncate(self.remoteCloseReason, 123), isReply=True)
  658. else:
  659. self.sendCloseFrame(code=WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL, isReply=True)
  660. if self.factory.isServer:
  661. # When we are a server, we immediately drop the TCP.
  662. self.dropConnection(abort=False)
  663. else:
  664. # When we are a client, we expect the server to drop the TCP,
  665. # and when the server fails to do so, a timeout in sendCloseFrame()
  666. # will set wasClean = False back again.
  667. pass
  668. elif self.state == WebSocketProtocol.STATE_CLOSED:
  669. # The peer initiated a closing handshake but dropped the TCP immediately.
  670. self.wasClean = False
  671. else:
  672. # STATE_PROXY_CONNECTING, STATE_CONNECTING
  673. raise Exception("logic error")
  674. def onServerConnectionDropTimeout(self):
  675. """
  676. We (a client) expected the peer (a server) to drop the connection,
  677. but it didn't (in time self.serverConnectionDropTimeout).
  678. So we drop the connection, but set self.wasClean = False.
  679. """
  680. self.serverConnectionDropTimeoutCall = None
  681. if self.state != WebSocketProtocol.STATE_CLOSED:
  682. self.wasClean = False
  683. self.wasNotCleanReason = 'WebSocket closing handshake timeout (server did not drop TCP connection in time)'
  684. self.wasServerConnectionDropTimeout = True
  685. self.dropConnection(abort=True)
  686. else:
  687. self.log.debug("skipping closing handshake timeout: server did indeed drop the connection in time")
  688. def onOpenHandshakeTimeout(self):
  689. """
  690. We expected the peer to complete the opening handshake with to us.
  691. It didn't do so (in time self.openHandshakeTimeout).
  692. So we drop the connection, but set self.wasClean = False.
  693. """
  694. self.openHandshakeTimeoutCall = None
  695. if self.state in [WebSocketProtocol.STATE_CONNECTING, WebSocketProtocol.STATE_PROXY_CONNECTING]:
  696. self.wasClean = False
  697. self.wasNotCleanReason = 'WebSocket opening handshake timeout (peer did not finish the opening handshake in time)'
  698. self.wasOpenHandshakeTimeout = True
  699. self.dropConnection(abort=True)
  700. elif self.state == WebSocketProtocol.STATE_OPEN:
  701. self.log.debug("skipping opening handshake timeout: WebSocket connection is open (opening handshake already finished)")
  702. elif self.state == WebSocketProtocol.STATE_CLOSING:
  703. self.log.debug("skipping opening handshake timeout: WebSocket connection is already closing ..")
  704. elif self.state == WebSocketProtocol.STATE_CLOSED:
  705. self.log.debug("skipping opening handshake timeout: WebSocket connection is already closed")
  706. else:
  707. # should not arrive here
  708. raise Exception("logic error")
  709. def onCloseHandshakeTimeout(self):
  710. """
  711. We expected the peer to respond to us initiating a close handshake. It didn't
  712. respond (in time self.closeHandshakeTimeout) with a close response frame though.
  713. So we drop the connection, but set self.wasClean = False.
  714. """
  715. self.closeHandshakeTimeoutCall = None
  716. if self.state != WebSocketProtocol.STATE_CLOSED:
  717. self.wasClean = False
  718. self.wasNotCleanReason = 'WebSocket closing handshake timeout (peer did not finish the closing handshake in time)'
  719. self.wasCloseHandshakeTimeout = True
  720. self.dropConnection(abort=True)
  721. else:
  722. self.log.debug('skipping closing handshake timeout: WebSocket connection is already closed')
  723. def onAutoPong(self, ping_sent, ping_seq, pong_received, pong_rtt, payload):
  724. """
  725. When doing automatic ping/pongs, this is called upon a successful pong.
  726. :param ping_sent: Posix time in ns when ping was sent.
  727. :param ping_seq: Sequence number of ping that was sent.
  728. :param pong_received: Posix time in ns when pong was received.
  729. :param pong_rtt: Pong roundtrip-time in ms measured.
  730. :param payload: The complete WebSocket ping/pong message payload
  731. (ping_sent 8 bytes big-endian | ping_seq 4 bytes big endian | max. 113 optional random bytes).
  732. """
  733. self.log.debug(
  734. "Auto ping/pong: received pending pong (size={size}) for auto-ping (sent={sent}, seq={seq}, received={received}) in RTT of {rtt} ms",
  735. size=len(payload), sent=ping_sent, seq=ping_seq, received=pong_received, rtt=pong_rtt)
  736. def onAutoPingTimeout(self):
  737. """
  738. When doing automatic ping/pongs to detect broken connection, the peer
  739. did not reply in time to our ping. We drop the connection.
  740. """
  741. self.wasClean = False
  742. self.wasNotCleanReason = 'WebSocket ping timeout (peer did not respond with pong in time)'
  743. self.autoPingTimeoutCall = None
  744. self.dropConnection(abort=True)
  745. def dropConnection(self, abort=False):
  746. """
  747. Drop the underlying TCP connection.
  748. """
  749. self.unregisterProducer()
  750. if self.state != WebSocketProtocol.STATE_CLOSED:
  751. if self.wasClean:
  752. self.log.debug('dropping connection to peer {peer} with abort={abort}', peer=self.peer, abort=abort)
  753. else:
  754. self.log.warn('dropping connection to peer {peer} with abort={abort}: {reason}', peer=self.peer, abort=abort, reason=self.wasNotCleanReason)
  755. self.droppedByMe = True
  756. # this code-path will be hit (*without* hitting
  757. # _connectionLost) in some timeout scenarios (unit-tests
  758. # cover these). However, sometimes we hit both.
  759. self.state = WebSocketProtocol.STATE_CLOSED
  760. txaio.resolve(self.is_closed, self)
  761. self._closeConnection(abort)
  762. else:
  763. self.log.debug('dropping connection to peer {peer} skipped - connection already closed', peer=self.peer)
  764. def _max_message_size_exceeded(self, msg_size, max_msg_size, reason):
  765. # hook that is fired when a message is (to be) received that is larger than what is configured to be handled
  766. if True:
  767. self._fail_connection(WebSocketProtocol.CLOSE_STATUS_CODE_MESSAGE_TOO_BIG, reason)
  768. else:
  769. raise PayloadExceededError(reason)
  770. def _fail_connection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason='going away'):
  771. """
  772. Fails the WebSocket connection.
  773. """
  774. if self.state != WebSocketProtocol.STATE_CLOSED:
  775. self.log.warn('failing WebSocket connection (code={code}): "{reason}"', code=code, reason=reason)
  776. self.failedByMe = True
  777. if self.failByDrop:
  778. # brutally drop the TCP connection
  779. self.wasClean = False
  780. self.wasNotCleanReason = 'I dropped the WebSocket TCP connection: {0}'.format(reason)
  781. self.dropConnection(abort=True)
  782. else:
  783. if self.state != WebSocketProtocol.STATE_CLOSING:
  784. # perform WebSocket closing handshake
  785. self.sendCloseFrame(code=code, reasonUtf8=encode_truncate(reason, 123), isReply=False)
  786. else:
  787. # already performing closing handshake .. we now drop the TCP
  788. # (this can happen e.g. if we encounter a 2nd protocol violation during closing HS)
  789. self.dropConnection(abort=False)
  790. else:
  791. self.log.debug("skip failing of connection since connection is already closed")
  792. def _protocol_violation(self, reason):
  793. """
  794. Fired when a WebSocket protocol violation/error occurs.
  795. :param reason: Protocol violation that was encountered (human readable).
  796. :type reason: str
  797. :returns: True, when any further processing should be discontinued.
  798. """
  799. self.log.debug("Protocol violation: {reason}", reason=reason)
  800. self._fail_connection(WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason)
  801. if self.failByDrop:
  802. return True
  803. else:
  804. # if we don't immediately drop the TCP, we need to skip the invalid frame
  805. # to continue to later receive the closing handshake reply
  806. return False
  807. def _invalid_payload(self, reason):
  808. """
  809. Fired when invalid payload is encountered. Currently, this only happens
  810. for text message when payload is invalid UTF-8 or close frames with
  811. close reason that is invalid UTF-8.
  812. :param reason: What was invalid for the payload (human readable).
  813. :type reason: str
  814. :returns: True, when any further processing should be discontinued.
  815. """
  816. self.log.debug("Invalid payload: {reason}", reason=reason)
  817. self._fail_connection(WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, reason)
  818. if self.failByDrop:
  819. return True
  820. else:
  821. # if we don't immediately drop the TCP, we need to skip the invalid frame
  822. # to continue to later receive the closing handshake reply
  823. return False
  824. def setTrackTimings(self, enable):
  825. """
  826. Enable/disable tracking of detailed timings.
  827. :param enable: Turn time tracking on/off.
  828. :type enable: bool
  829. """
  830. if not hasattr(self, 'trackTimings') or self.trackTimings != enable:
  831. self.trackTimings = enable
  832. if self.trackTimings:
  833. self.trackedTimings = Timings()
  834. else:
  835. self.trackedTimings = None
  836. def _connectionMade(self):
  837. """
  838. This is called by network framework when a new TCP connection has been established
  839. and handed over to a Protocol instance (an instance of this class).
  840. """
  841. # copy default options from factory (so we are not affected by changed on
  842. # those), but only copy if not already set on protocol instance (allow
  843. # to set configuration individually)
  844. #
  845. configAttrLog = []
  846. for configAttr in self.CONFIG_ATTRS:
  847. if not hasattr(self, configAttr):
  848. setattr(self, configAttr, getattr(self.factory, configAttr))
  849. configAttrSource = self.factory.__class__.__name__
  850. else:
  851. configAttrSource = self.__class__.__name__
  852. configAttrLog.append((configAttr, getattr(self, configAttr), configAttrSource))
  853. self.log.debug("\n{attrs}", attrs=pformat(configAttrLog))
  854. # permessage-compress extension
  855. self._perMessageCompress = None
  856. # Time tracking
  857. self.trackedTimings = None
  858. self.setTrackTimings(self.trackTimings)
  859. # Traffic stats
  860. self.trafficStats = TrafficStats()
  861. # initial state
  862. if not self.factory.isServer and self.factory.proxy is not None:
  863. self.state = WebSocketProtocol.STATE_PROXY_CONNECTING
  864. else:
  865. self.state = WebSocketProtocol.STATE_CONNECTING
  866. self.send_state = WebSocketProtocol.SEND_STATE_GROUND
  867. self.data = b''
  868. # for chopped/synched sends, we need to queue to maintain
  869. # ordering when recalling the reactor to actually "force"
  870. # the octets to wire (see test/trickling in the repo)
  871. self.send_queue = deque()
  872. self.triggered = False
  873. # incremental UTF8 validator
  874. self.utf8validator = Utf8Validator()
  875. # track when frame/message payload sizes (incoming) were exceeded
  876. self.wasMaxFramePayloadSizeExceeded = False
  877. self.wasMaxMessagePayloadSizeExceeded = False
  878. # the following vars are related to connection close handling/tracking
  879. # True, iff I have initiated closing HS (that is, did send close first)
  880. self.closedByMe = False
  881. # True, iff I have failed the WS connection (i.e. due to protocol error)
  882. # Failing can be either by initiating close HS or brutal drop (this is
  883. # controlled by failByDrop option)
  884. self.failedByMe = False
  885. # True, iff I dropped the TCP connection (called transport.loseConnection())
  886. self.droppedByMe = False
  887. # True, iff full WebSocket closing handshake was performed (close frame sent
  888. # and received) _and_ the server dropped the TCP (which is its responsibility)
  889. self.wasClean = False
  890. # When self.wasClean = False, the reason (what happened)
  891. self.wasNotCleanReason = None
  892. # When we are a client, and we expected the server to drop the TCP, but that
  893. # didn't happen in time, this gets True
  894. self.wasServerConnectionDropTimeout = False
  895. # When the initial WebSocket opening handshake times out, this gets True
  896. self.wasOpenHandshakeTimeout = False
  897. # When we initiated a closing handshake, but the peer did not respond in
  898. # time, this gets True
  899. self.wasCloseHandshakeTimeout = False
  900. # True, iff I dropped the TCP connection because we fully served the
  901. # Flash Socket Policy File after a policy file request.
  902. self.wasServingFlashSocketPolicyFile = False
  903. # The close code I sent in close frame (if any)
  904. self.localCloseCode = None
  905. # The close reason I sent in close frame (if any)
  906. self.localCloseReason = None
  907. # The close code the peer sent me in close frame (if any)
  908. self.remoteCloseCode = None
  909. # The close reason the peer sent me in close frame (if any)
  910. self.remoteCloseReason = None
  911. # timers, which might get set up later, and remembered here to get canceled
  912. # when appropriate
  913. if not self.factory.isServer:
  914. self.serverConnectionDropTimeoutCall = None
  915. self.openHandshakeTimeoutCall = None
  916. self.closeHandshakeTimeoutCall = None
  917. self.autoPingTimeoutCall = None
  918. self.autoPingPending = None
  919. self.autoPingPendingCall = None
  920. self.autoPingPendingSeq = 0
  921. self.autoPingPendingSent = None
  922. # set opening handshake timeout handler
  923. if self.openHandshakeTimeout > 0:
  924. self.openHandshakeTimeoutCall = self.factory._batched_timer.call_later(
  925. self.openHandshakeTimeout,
  926. self.onOpenHandshakeTimeout,
  927. )
  928. def _connectionLost(self, reason):
  929. """
  930. This is called by network framework when a transport connection was
  931. lost.
  932. """
  933. # cancel any server connection drop timer if present
  934. #
  935. self.log.debug('_connectionLost: {reason}', reason=reason)
  936. if not self.factory.isServer and self.serverConnectionDropTimeoutCall is not None:
  937. self.log.debug("serverConnectionDropTimeoutCall.cancel")
  938. self.serverConnectionDropTimeoutCall.cancel()
  939. self.serverConnectionDropTimeoutCall = None
  940. # cleanup auto ping/pong timers
  941. #
  942. if self.autoPingPendingCall:
  943. self.log.debug("Auto ping/pong: canceling autoPingPendingCall upon lost connection")
  944. self.autoPingPendingCall.cancel()
  945. self.autoPingPendingCall = None
  946. if self.autoPingTimeoutCall:
  947. self.log.debug("Auto ping/pong: canceling autoPingTimeoutCall upon lost connection")
  948. self.autoPingTimeoutCall.cancel()
  949. self.autoPingTimeoutCall = None
  950. # our handshake timeout may not have fired
  951. if self.openHandshakeTimeoutCall is not None:
  952. self.openHandshakeTimeoutCall.cancel()
  953. self.openHandshakeTimeoutCall = None
  954. # check required here because in some scenarios dropConnection
  955. # will already have resolved the Future/Deferred.
  956. if self.state != WebSocketProtocol.STATE_CLOSED:
  957. self.state = WebSocketProtocol.STATE_CLOSED
  958. txaio.resolve(self.is_closed, self)
  959. if self.wasServingFlashSocketPolicyFile:
  960. self.log.debug("connection dropped after serving Flash Socket Policy File")
  961. else:
  962. if not self.wasClean:
  963. if not self.droppedByMe and self.wasNotCleanReason is None:
  964. reason_value = getattr(reason, 'value', None)
  965. reason_string = None if not reason_value else _maybe_tls_reason(reason_value)
  966. if reason_string:
  967. self.wasNotCleanReason = reason_string
  968. else:
  969. self.wasNotCleanReason = 'peer dropped the TCP connection without previous WebSocket closing handshake'
  970. self._onClose(self.wasClean, WebSocketProtocol.CLOSE_STATUS_CODE_ABNORMAL_CLOSE, 'connection was closed uncleanly ("{}")'.format(self.wasNotCleanReason))
  971. else:
  972. self._onClose(self.wasClean, self.remoteCloseCode, self.remoteCloseReason)
  973. # XXX could self.fire("close", ...) here if we want?
  974. def logRxOctets(self, data):
  975. """
  976. Hook fired right after raw octets have been received, but only when
  977. self.logOctets == True.
  978. """
  979. self.log.debug(
  980. "RX Octets from {peer} : octets = {octets}",
  981. peer=self.peer,
  982. octets=_LazyHexFormatter(data),
  983. )
  984. def logTxOctets(self, data, sync):
  985. """
  986. Hook fired right after raw octets have been sent, but only when
  987. self.logOctets == True.
  988. """
  989. self.log.debug(
  990. "TX Octets to {peer} : sync = {sync}, octets = {octets}",
  991. peer=self.peer,
  992. sync=sync,
  993. octets=_LazyHexFormatter(data),
  994. )
  995. def logRxFrame(self, frameHeader, payload):
  996. """
  997. Hook fired right after WebSocket frame has been received and decoded,
  998. but only when self.logFrames == True.
  999. """
  1000. data = b''.join(payload)
  1001. self.log.debug(
  1002. "RX Frame from {peer} : fin = {fin}, rsv = {rsv}, opcode = {opcode}"
  1003. ", mask = {mask}, length = {length}, payload = {payload}",
  1004. peer=self.peer,
  1005. fin=frameHeader.fin,
  1006. rsv=frameHeader.rsv,
  1007. opcode=frameHeader.opcode,
  1008. mask=binascii.b2a_hex(frameHeader.mask) if frameHeader.mask else "-",
  1009. length=frameHeader.length,
  1010. payload=repr(data) if frameHeader.opcode == 1 else _LazyHexFormatter(data),
  1011. )
  1012. def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync):
  1013. """
  1014. Hook fired right after WebSocket frame has been encoded and sent, but
  1015. only when self.logFrames == True.
  1016. """
  1017. self.log.debug(
  1018. "TX Frame to {peer} : fin = {fin}, rsv = {rsv}, opcode = {opcode}, "
  1019. "mask = {mask}, length = {length}, repeat_length = {repeat_length},"
  1020. " chopsize = {chopsize}, sync = {sync}, payload = {payload}",
  1021. peer=self.peer,
  1022. fin=frameHeader.fin,
  1023. rsv=frameHeader.rsv,
  1024. opcode=frameHeader.opcode,
  1025. mask=binascii.b2a_hex(frameHeader.mask) if frameHeader.mask else "-",
  1026. length=frameHeader.length,
  1027. repeat_length=repeatLength,
  1028. chopsize=chopsize,
  1029. sync=sync,
  1030. payload=repr(payload) if frameHeader.opcode == 1 else _LazyHexFormatter(payload),
  1031. )
  1032. def _dataReceived(self, data):
  1033. """
  1034. This is called by network framework upon receiving data on transport
  1035. connection.
  1036. """
  1037. if self.state == WebSocketProtocol.STATE_OPEN:
  1038. self.trafficStats.incomingOctetsWireLevel += len(data)
  1039. elif self.state == WebSocketProtocol.STATE_CONNECTING or self.state == WebSocketProtocol.STATE_PROXY_CONNECTING:
  1040. self.trafficStats.preopenIncomingOctetsWireLevel += len(data)
  1041. if self.logOctets:
  1042. self.logRxOctets(data)
  1043. self.data += data
  1044. self.consumeData()
  1045. def consumeData(self):
  1046. """
  1047. Consume buffered (incoming) data.
  1048. """
  1049. # WebSocket is open (handshake was completed) or close was sent
  1050. #
  1051. if self.state == WebSocketProtocol.STATE_OPEN or self.state == WebSocketProtocol.STATE_CLOSING:
  1052. # process until no more buffered data left or WS was closed
  1053. #
  1054. while self.processData() and self.state != WebSocketProtocol.STATE_CLOSED:
  1055. pass
  1056. # need to establish proxy connection
  1057. #
  1058. elif self.state == WebSocketProtocol.STATE_PROXY_CONNECTING:
  1059. self.processProxyConnect()
  1060. # WebSocket needs handshake
  1061. #
  1062. elif self.state == WebSocketProtocol.STATE_CONNECTING:
  1063. # the implementation of processHandshake() in derived
  1064. # class needs to perform client or server handshake
  1065. # from other party here ..
  1066. #
  1067. self.processHandshake()
  1068. # we failed the connection .. don't process any more data!
  1069. #
  1070. elif self.state == WebSocketProtocol.STATE_CLOSED:
  1071. # ignore any data received after WS was closed
  1072. #
  1073. self.log.debug("received data in STATE_CLOSED")
  1074. # should not arrive here (invalid state)
  1075. #
  1076. else:
  1077. raise Exception("invalid state")
  1078. def processProxyConnect(self):
  1079. """
  1080. Process proxy connect.
  1081. """
  1082. raise Exception("must implement proxy connect (client or server) in derived class")
  1083. def processHandshake(self):
  1084. """
  1085. Process WebSocket handshake.
  1086. """
  1087. raise Exception("must implement handshake (client or server) in derived class")
  1088. def _trigger(self):
  1089. """
  1090. Trigger sending stuff from send queue (which is only used for
  1091. chopped/synched writes).
  1092. """
  1093. if not self.triggered:
  1094. self.triggered = True
  1095. self._send()
  1096. def _send(self):
  1097. """
  1098. Send out stuff from send queue. For details how this works, see
  1099. test/trickling in the repo.
  1100. """
  1101. if len(self.send_queue) > 0:
  1102. e = self.send_queue.popleft()
  1103. if self.state != WebSocketProtocol.STATE_CLOSED:
  1104. self.transport.write(e[0])
  1105. if self.state == WebSocketProtocol.STATE_OPEN:
  1106. self.trafficStats.outgoingOctetsWireLevel += len(e[0])
  1107. elif self.state == WebSocketProtocol.STATE_CONNECTING or self.state == WebSocketProtocol.STATE_PROXY_CONNECTING:
  1108. self.trafficStats.preopenOutgoingOctetsWireLevel += len(e[0])
  1109. if self.logOctets:
  1110. self.logTxOctets(e[0], e[1])
  1111. else:
  1112. self.log.debug("skipped delayed write, since connection is closed")
  1113. # we need to reenter the reactor to make the latter
  1114. # reenter the OS network stack, so that octets
  1115. # can get on the wire. Note: this is a "heuristic",
  1116. # since there is no (easy) way to really force out
  1117. # octets from the OS network stack to wire.
  1118. txaio.call_later(WebSocketProtocol._QUEUED_WRITE_DELAY, self._send)
  1119. else:
  1120. self.triggered = False
  1121. def sendData(self, data, sync=False, chopsize=None):
  1122. """
  1123. Wrapper for self.transport.write which allows to give a chopsize.
  1124. When asked to chop up writing to TCP stream, we write only chopsize
  1125. octets and then give up control to select() in underlying reactor so
  1126. that bytes get onto wire immediately. Note that this is different from
  1127. and unrelated to WebSocket data message fragmentation. Note that this
  1128. is also different from the TcpNoDelay option which can be set on the
  1129. socket.
  1130. """
  1131. if chopsize and chopsize > 0:
  1132. i = 0
  1133. n = len(data)
  1134. done = False
  1135. while not done:
  1136. j = i + chopsize
  1137. if j >= n:
  1138. done = True
  1139. j = n
  1140. self.send_queue.append((data[i:j], True))
  1141. i += chopsize
  1142. self._trigger()
  1143. else:
  1144. if sync or len(self.send_queue) > 0:
  1145. self.send_queue.append((data, sync))
  1146. self._trigger()
  1147. else:
  1148. self.transport.write(data)
  1149. self.log.debug('{func} sent {data_len} bytes for peer {peer}',
  1150. func=hltype(self.sendData),
  1151. peer=hlval(self.peer),
  1152. data_len=hlval(len(data)))
  1153. if self.state == WebSocketProtocol.STATE_OPEN:
  1154. self.trafficStats.outgoingOctetsWireLevel += len(data)
  1155. elif self.state == WebSocketProtocol.STATE_CONNECTING or self.state == WebSocketProtocol.STATE_PROXY_CONNECTING:
  1156. self.trafficStats.preopenOutgoingOctetsWireLevel += len(data)
  1157. if self.logOctets:
  1158. self.logTxOctets(data, False)
  1159. def sendPreparedMessage(self, preparedMsg):
  1160. """
  1161. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPreparedMessage`
  1162. """
  1163. if self._perMessageCompress is None or preparedMsg.doNotCompress:
  1164. self.sendData(preparedMsg.payloadHybi)
  1165. else:
  1166. self.sendMessage(preparedMsg.payload, preparedMsg.binary)
  1167. def processData(self):
  1168. """
  1169. After WebSocket handshake has been completed, this procedure will do
  1170. all subsequent processing of incoming bytes.
  1171. """
  1172. buffered_len = len(self.data)
  1173. # outside a frame, that is we are awaiting data which starts a new frame
  1174. #
  1175. if self.current_frame is None:
  1176. # need minimum of 2 octets to for new frame
  1177. #
  1178. if buffered_len >= 2:
  1179. # FIN, RSV, OPCODE
  1180. #
  1181. b = self.data[0]
  1182. frame_fin = (b & 0x80) != 0
  1183. frame_rsv = (b & 0x70) >> 4
  1184. frame_opcode = b & 0x0f
  1185. # MASK, PAYLOAD LEN 1
  1186. #
  1187. b = self.data[1]
  1188. frame_masked = (b & 0x80) != 0
  1189. frame_payload_len1 = b & 0x7f
  1190. # MUST be 0 when no extension defining
  1191. # the semantics of RSV has been negotiated
  1192. #
  1193. if frame_rsv != 0:
  1194. if self._perMessageCompress is not None and frame_rsv == 4:
  1195. pass
  1196. else:
  1197. if self._protocol_violation('RSV = {} and no extension negotiated'.format(frame_rsv)):
  1198. return False
  1199. # all client-to-server frames MUST be masked
  1200. #
  1201. if self.factory.isServer and self.requireMaskedClientFrames and not frame_masked:
  1202. if self._protocol_violation('unmasked client-to-server frame'):
  1203. return False
  1204. # all server-to-client frames MUST NOT be masked
  1205. #
  1206. if not self.factory.isServer and not self.acceptMaskedServerFrames and frame_masked:
  1207. if self._protocol_violation('masked server-to-client frame'):
  1208. return False
  1209. # check frame
  1210. #
  1211. if frame_opcode > 7: # control frame (have MSB in opcode set)
  1212. # control frames MUST NOT be fragmented
  1213. #
  1214. if not frame_fin:
  1215. if self._protocol_violation('fragmented control frame'):
  1216. return False
  1217. # control frames MUST have payload 125 octets or less
  1218. #
  1219. if frame_payload_len1 > 125:
  1220. if self._protocol_violation('control frame with payload length > 125 octets'):
  1221. return False
  1222. # check for reserved control frame opcodes
  1223. #
  1224. if frame_opcode not in [8, 9, 10]:
  1225. if self._protocol_violation('control frame using reserved opcode {}'.format(frame_opcode)):
  1226. return False
  1227. # close frame : if there is a body, the first two bytes of the body MUST be a 2-byte
  1228. # unsigned integer (in network byte order) representing a status code
  1229. #
  1230. if frame_opcode == 8 and frame_payload_len1 == 1:
  1231. if self._protocol_violation('received close control frame with payload len 1'):
  1232. return False
  1233. # control frames MUST NOT be compressed
  1234. #
  1235. if self._perMessageCompress is not None and frame_rsv == 4:
  1236. if self._protocol_violation('received compressed control frame [{}]'.format(self._perMessageCompress.EXTENSION_NAME)):
  1237. return False
  1238. else: # data frame
  1239. # check for reserved data frame opcodes
  1240. #
  1241. if frame_opcode not in [0, 1, 2]:
  1242. if self._protocol_violation('data frame using reserved opcode {}'.format(frame_opcode)):
  1243. return False
  1244. # check opcode vs message fragmentation state 1/2
  1245. #
  1246. if not self.inside_message and frame_opcode == 0:
  1247. if self._protocol_violation('received continuation data frame outside fragmented message'):
  1248. return False
  1249. # check opcode vs message fragmentation state 2/2
  1250. #
  1251. if self.inside_message and frame_opcode != 0:
  1252. if self._protocol_violation('received non-continuation data frame while inside fragmented message'):
  1253. return False
  1254. # continuation data frames MUST NOT have the compressed bit set
  1255. #
  1256. if self._perMessageCompress is not None and frame_rsv == 4 and self.inside_message:
  1257. if self._protocol_violation('received continuation data frame with compress bit set [{}]'.format(self._perMessageCompress.EXTENSION_NAME)):
  1258. return False
  1259. # compute complete header length
  1260. #
  1261. if frame_masked:
  1262. mask_len = 4
  1263. else:
  1264. mask_len = 0
  1265. if frame_payload_len1 < 126:
  1266. frame_header_len = 2 + mask_len
  1267. elif frame_payload_len1 == 126:
  1268. frame_header_len = 2 + 2 + mask_len
  1269. elif frame_payload_len1 == 127:
  1270. frame_header_len = 2 + 8 + mask_len
  1271. else:
  1272. raise Exception("logic error")
  1273. # only proceed when we have enough data buffered for complete
  1274. # frame header (which includes extended payload len + mask)
  1275. #
  1276. if buffered_len >= frame_header_len:
  1277. # minimum frame header length (already consumed)
  1278. #
  1279. i = 2
  1280. # extract extended payload length
  1281. #
  1282. if frame_payload_len1 == 126:
  1283. frame_payload_len = struct.unpack("!H", self.data[i:i + 2])[0]
  1284. if frame_payload_len < 126:
  1285. if self._protocol_violation('invalid data frame length (not using minimal length encoding)'):
  1286. return False
  1287. i += 2
  1288. elif frame_payload_len1 == 127:
  1289. frame_payload_len = struct.unpack("!Q", self.data[i:i + 8])[0]
  1290. if frame_payload_len > 0x7FFFFFFFFFFFFFFF: # 2**63
  1291. if self._protocol_violation('invalid data frame length (>2^63)'):
  1292. return False
  1293. if frame_payload_len < 65536:
  1294. if self._protocol_violation('invalid data frame length (not using minimal length encoding)'):
  1295. return False
  1296. i += 8
  1297. else:
  1298. frame_payload_len = frame_payload_len1
  1299. # when payload is masked, extract frame mask
  1300. #
  1301. frame_mask = None
  1302. if frame_masked:
  1303. frame_mask = self.data[i:i + 4]
  1304. i += 4
  1305. if frame_masked and frame_payload_len > 0 and self.applyMask:
  1306. self.current_frame_masker = create_xor_masker(frame_mask, frame_payload_len)
  1307. else:
  1308. self.current_frame_masker = XorMaskerNull()
  1309. # remember rest (payload of current frame after header and everything thereafter)
  1310. #
  1311. self.data = self.data[i:]
  1312. # ok, got complete frame header
  1313. #
  1314. self.current_frame = FrameHeader(frame_opcode,
  1315. frame_fin,
  1316. frame_rsv,
  1317. frame_payload_len,
  1318. frame_mask)
  1319. # process begin on new frame
  1320. #
  1321. self.onFrameBegin()
  1322. # reprocess when frame has no payload or and buffered data left
  1323. #
  1324. return frame_payload_len == 0 or len(self.data) > 0
  1325. else:
  1326. return False # need more data
  1327. else:
  1328. return False # need more data
  1329. # inside a started frame
  1330. #
  1331. else:
  1332. # cut out rest of frame payload
  1333. #
  1334. rest = self.current_frame.length - self.current_frame_masker.pointer()
  1335. if buffered_len >= rest:
  1336. data = self.data[:rest]
  1337. length = rest
  1338. self.data = self.data[rest:]
  1339. else:
  1340. data = self.data
  1341. length = buffered_len
  1342. self.data = b''
  1343. if length > 0:
  1344. # unmask payload
  1345. #
  1346. payload = self.current_frame_masker.process(data)
  1347. else:
  1348. # we also process empty payloads, since we need to fire
  1349. # our hooks (at least for streaming processing, this is
  1350. # necessary for correct protocol state transitioning)
  1351. #
  1352. payload = b''
  1353. # process frame data
  1354. #
  1355. fr = self.onFrameData(payload)
  1356. # noinspection PySimplifyBooleanCheck
  1357. if fr is False:
  1358. return False
  1359. # fire frame end handler when frame payload is complete
  1360. #
  1361. if self.current_frame_masker.pointer() == self.current_frame.length:
  1362. fr = self.onFrameEnd()
  1363. # noinspection PySimplifyBooleanCheck
  1364. if fr is False:
  1365. return False
  1366. # reprocess when no error occurred and buffered data left
  1367. #
  1368. return len(self.data) > 0
  1369. def onFrameBegin(self):
  1370. """
  1371. Begin of receive new frame.
  1372. """
  1373. if self.current_frame.opcode > 7:
  1374. self.control_frame_data = []
  1375. else:
  1376. # new message started
  1377. #
  1378. if not self.inside_message:
  1379. self.inside_message = True
  1380. # setup decompressor
  1381. #
  1382. if self._perMessageCompress is not None and self.current_frame.rsv == 4:
  1383. self._isMessageCompressed = True
  1384. self._perMessageCompress.start_decompress_message()
  1385. else:
  1386. self._isMessageCompressed = False
  1387. # setup UTF8 validator
  1388. #
  1389. if self.current_frame.opcode == WebSocketProtocol.MESSAGE_TYPE_TEXT and self.utf8validateIncoming:
  1390. self.utf8validator.reset()
  1391. self.utf8validateIncomingCurrentMessage = True
  1392. self.utf8validateLast = (True, True, 0, 0)
  1393. else:
  1394. self.utf8validateIncomingCurrentMessage = False
  1395. # track timings
  1396. #
  1397. if self.trackedTimings:
  1398. self.trackedTimings.track("onMessageBegin")
  1399. # fire onMessageBegin
  1400. #
  1401. self._onMessageBegin(self.current_frame.opcode == WebSocketProtocol.MESSAGE_TYPE_BINARY)
  1402. self._onMessageFrameBegin(self.current_frame.length)
  1403. def onFrameData(self, payload):
  1404. """
  1405. New data received within frame.
  1406. """
  1407. if self.current_frame.opcode > 7:
  1408. self.control_frame_data.append(payload)
  1409. else:
  1410. # decompress frame payload
  1411. #
  1412. if self._isMessageCompressed:
  1413. compressedLen = len(payload)
  1414. self.log.debug(
  1415. "RX compressed [length]: octets",
  1416. legnth=compressedLen,
  1417. octets=_LazyHexFormatter(payload),
  1418. )
  1419. # XXX oberstet
  1420. payload = self._perMessageCompress.decompress_message_data(payload)
  1421. uncompressedLen = len(payload)
  1422. else:
  1423. l = len(payload)
  1424. compressedLen = l
  1425. uncompressedLen = l
  1426. if self.state == WebSocketProtocol.STATE_OPEN:
  1427. self.trafficStats.incomingOctetsWebSocketLevel += compressedLen
  1428. self.trafficStats.incomingOctetsAppLevel += uncompressedLen
  1429. # incrementally validate UTF-8 payload
  1430. #
  1431. if self.utf8validateIncomingCurrentMessage:
  1432. self.utf8validateLast = self.utf8validator.validate(payload)
  1433. if not self.utf8validateLast[0]:
  1434. if self._invalid_payload('encountered invalid UTF-8 while processing text message at payload octet index {}'.format(self.utf8validateLast[3])):
  1435. return False
  1436. self._onMessageFrameData(payload)
  1437. def onFrameEnd(self):
  1438. """
  1439. End of frame received.
  1440. """
  1441. if self.current_frame.opcode > 7:
  1442. if self.logFrames:
  1443. self.logRxFrame(self.current_frame, self.control_frame_data)
  1444. self.processControlFrame()
  1445. else:
  1446. if self.state == WebSocketProtocol.STATE_OPEN:
  1447. self.trafficStats.incomingWebSocketFrames += 1
  1448. if self.logFrames:
  1449. self.logRxFrame(self.current_frame, self.frame_data)
  1450. self._onMessageFrameEnd()
  1451. if self.autoPingTimeoutCall and self.autoPingRestartOnAnyTraffic:
  1452. # cancel a pending ping timeout already by having received a data frame
  1453. # note that this is slightly wrong, but see _cancelAutoPingTimeoutCall and:
  1454. # https://github.com/crossbario/autobahn-python/issues/1327
  1455. self._cancelAutoPingTimeoutCall()
  1456. if self.current_frame.fin:
  1457. # handle end of compressed message
  1458. #
  1459. if self._isMessageCompressed:
  1460. self._perMessageCompress.end_decompress_message()
  1461. # verify UTF8 has actually ended
  1462. #
  1463. if self.utf8validateIncomingCurrentMessage:
  1464. if not self.utf8validateLast[1]:
  1465. if self._invalid_payload('UTF-8 text message payload ended within Unicode code point at payload octet index {}'.format(self.utf8validateLast[3])):
  1466. return False
  1467. if self.state == WebSocketProtocol.STATE_OPEN:
  1468. self.trafficStats.incomingWebSocketMessages += 1
  1469. self._onMessageEnd()
  1470. self.inside_message = False
  1471. self.current_frame = None
  1472. def processControlFrame(self):
  1473. """
  1474. Process a completely received control frame.
  1475. """
  1476. payload = b''.join(self.control_frame_data)
  1477. self.control_frame_data = None
  1478. # CLOSE frame
  1479. #
  1480. if self.current_frame.opcode == 8:
  1481. code = None
  1482. reasonRaw = None
  1483. ll = len(payload)
  1484. if ll > 1:
  1485. code = struct.unpack("!H", payload[0:2])[0]
  1486. if ll > 2:
  1487. reasonRaw = payload[2:]
  1488. if self.onCloseFrame(code, reasonRaw):
  1489. return False
  1490. # PING frame
  1491. #
  1492. elif self.current_frame.opcode == 9:
  1493. self._onPing(payload)
  1494. # PONG frame
  1495. #
  1496. elif self.current_frame.opcode == 10:
  1497. # auto ping/pong processing
  1498. #
  1499. if self.autoPingPending:
  1500. try:
  1501. if payload == self.autoPingPending:
  1502. # self.autoPingPendingSent
  1503. ping_sent = struct.unpack('>Q', payload[:8])[0]
  1504. # self.autoPingPendingSeq
  1505. ping_seq = struct.unpack('>L', payload[8:12])[0]
  1506. pong_received = time.time_ns()
  1507. pong_rtt = int((pong_received - ping_sent) / 10**6)
  1508. self.onAutoPong(ping_sent, ping_seq, pong_received, pong_rtt, payload)
  1509. if self.autoPingTimeoutCall:
  1510. self.autoPingTimeoutCall.cancel()
  1511. self.autoPingPending = None
  1512. self.autoPingPendingSent = None
  1513. self.autoPingTimeoutCall = None
  1514. if self.autoPingInterval:
  1515. self.autoPingPendingCall = self.factory._batched_timer.call_later(
  1516. self.autoPingInterval,
  1517. self._sendAutoPing,
  1518. )
  1519. else:
  1520. self.log.warn("Auto ping/pong: received non-pending pong")
  1521. except:
  1522. self.log.warn("Auto ping/pong: received non-pending pong")
  1523. # fire app-level callback
  1524. #
  1525. self._onPong(payload)
  1526. else:
  1527. # we might arrive here, when protocolViolation
  1528. # wants us to continue anyway
  1529. pass
  1530. return True
  1531. def sendFrame(self,
  1532. opcode,
  1533. payload=b'',
  1534. fin=True,
  1535. rsv=0,
  1536. mask=None,
  1537. payload_len=None,
  1538. chopsize=None,
  1539. sync=False):
  1540. """
  1541. Send out frame. Normally only used internally via sendMessage(),
  1542. sendPing(), sendPong() and sendClose().
  1543. This method deliberately allows to send invalid frames (that is frames
  1544. invalid per-se, or frames invalid because of protocol state). Other
  1545. than in fuzzing servers, calling methods will ensure that no invalid
  1546. frames are sent.
  1547. In addition, this method supports explicit specification of payload
  1548. length. When payload_len is given, it will always write that many
  1549. octets to the stream. It'll wrap within payload, resending parts of
  1550. that when more octets were requested The use case is again for fuzzing
  1551. server which want to sent increasing amounts of payload data to peers
  1552. without having to construct potentially large messages themselves.
  1553. """
  1554. if payload_len is not None:
  1555. if len(payload) < 1:
  1556. raise Exception('cannot construct repeated payload with length {} from payload of length {}'.format(payload_len, len(payload)))
  1557. l = payload_len
  1558. pl = b''.join([payload for _ in range(payload_len // len(payload))]) + payload[:payload_len % len(payload)]
  1559. else:
  1560. l = len(payload)
  1561. pl = payload
  1562. # first byte
  1563. #
  1564. b0 = 0
  1565. if fin:
  1566. b0 |= (1 << 7)
  1567. b0 |= (rsv % 8) << 4
  1568. b0 |= opcode % 128
  1569. # second byte, payload len bytes and mask
  1570. #
  1571. b1 = 0
  1572. if mask or (not self.factory.isServer and self.maskClientFrames) or (self.factory.isServer and self.maskServerFrames):
  1573. b1 |= 1 << 7
  1574. if not mask:
  1575. # note: the RFC mentions "cryptographic randomness"
  1576. # for the masks, which *does* make sense for browser
  1577. # implementations, but not in this case -- for
  1578. # example, a user of this library could just
  1579. # monkey-patch os.urandom (or getrandbits) and predict
  1580. # the masks easily. See issue 758 for more.
  1581. mask = struct.pack("!I", random.getrandbits(32))
  1582. mv = mask
  1583. else:
  1584. mv = b''
  1585. # mask frame payload
  1586. #
  1587. if l > 0 and self.applyMask:
  1588. masker = create_xor_masker(mask, l)
  1589. plm = masker.process(pl)
  1590. else:
  1591. plm = pl
  1592. else:
  1593. mv = b''
  1594. plm = pl
  1595. el = b''
  1596. if l <= 125:
  1597. b1 |= l
  1598. elif l <= 0xFFFF:
  1599. b1 |= 126
  1600. el = struct.pack("!H", l)
  1601. elif l <= 0x7FFFFFFFFFFFFFFF:
  1602. b1 |= 127
  1603. el = struct.pack("!Q", l)
  1604. else:
  1605. raise Exception("invalid payload length")
  1606. raw = b''.join([b0.to_bytes(1, 'big'), b1.to_bytes(1, 'big'), el, mv, plm])
  1607. if opcode in [0, 1, 2]:
  1608. self.trafficStats.outgoingWebSocketFrames += 1
  1609. if self.logFrames:
  1610. frameHeader = FrameHeader(opcode, fin, rsv, l, mask)
  1611. self.logTxFrame(frameHeader, payload, payload_len, chopsize, sync)
  1612. # send frame octets
  1613. #
  1614. self.sendData(raw, sync, chopsize)
  1615. def sendPing(self, payload=None):
  1616. """
  1617. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPing`
  1618. """
  1619. if self.state != WebSocketProtocol.STATE_OPEN:
  1620. return
  1621. if payload:
  1622. l = len(payload)
  1623. if l > 125:
  1624. raise Exception('invalid payload for PING (payload length must be <= 125, was {})'.format(l))
  1625. self.sendFrame(opcode=9, payload=payload)
  1626. else:
  1627. self.sendFrame(opcode=9)
  1628. def _sendAutoPing(self):
  1629. # Sends an automatic ping and sets up a timeout.
  1630. self.log.debug("Auto ping/pong: sending ping auto-ping/pong")
  1631. self.autoPingPendingCall = None
  1632. self.autoPingPendingSent = time.time_ns()
  1633. self.autoPingPendingSeq += 1
  1634. self.autoPingPending = b''.join([struct.pack('>Q', self.autoPingPendingSent),
  1635. struct.pack('>L', self.autoPingPendingSeq),
  1636. os.urandom(self.autoPingSize - 12)])
  1637. self.sendPing(self.autoPingPending)
  1638. if self.autoPingTimeout:
  1639. self.log.debug(
  1640. "Expecting pong in {seconds} seconds for auto-ping ({size} bytes)",
  1641. seconds=self.autoPingTimeout,
  1642. size=len(self.autoPingPending),
  1643. )
  1644. self.autoPingTimeoutCall = self.factory._batched_timer.call_later(
  1645. self.autoPingTimeout,
  1646. self.onAutoPingTimeout,
  1647. )
  1648. def _cancelAutoPingTimeoutCall(self):
  1649. """
  1650. When data is received from client, use it in leu of timely PONG response - cancel pending timeout call
  1651. that will drop connection. See https://github.com/crossbario/autobahn-python/issues/1327
  1652. """
  1653. self.log.debug("Cancelling autoPingTimeoutCall due to incoming data")
  1654. self.autoPingTimeoutCall.cancel()
  1655. self.autoPingTimeoutCall = None
  1656. # clear pending auto ping data, as current ping must be discarded
  1657. self.autoPingPending = None
  1658. if self.autoPingPendingCall:
  1659. self.autoPingPendingCall.cancel()
  1660. self.autoPingPendingCall = None
  1661. # schedule new autoping, without waiting for PONG, this will setup new autoPingPending
  1662. if self.autoPingInterval:
  1663. self.log.debug("Scheduling auto-ping/pong")
  1664. self.autoPingPendingCall = self.factory._batched_timer.call_later(
  1665. self.autoPingInterval,
  1666. self._sendAutoPing,
  1667. )
  1668. def sendPong(self, payload=None):
  1669. """
  1670. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPong`
  1671. """
  1672. if self.state != WebSocketProtocol.STATE_OPEN:
  1673. return
  1674. if payload:
  1675. l = len(payload)
  1676. if l > 125:
  1677. raise Exception('invalid payload for PONG (payload length must be <= 125, was {})'.format(l))
  1678. self.sendFrame(opcode=10, payload=payload)
  1679. else:
  1680. self.sendFrame(opcode=10)
  1681. def sendCloseFrame(self, code=None, reasonUtf8=None, isReply=False):
  1682. """
  1683. Send a close frame and update protocol state. Note, that this is
  1684. an internal method which deliberately allows not send close
  1685. frame with invalid payload.
  1686. """
  1687. if self.state == WebSocketProtocol.STATE_CLOSING:
  1688. self.log.debug("ignoring sendCloseFrame since connection is closing")
  1689. elif self.state == WebSocketProtocol.STATE_CLOSED:
  1690. self.log.debug("ignoring sendCloseFrame since connection already closed")
  1691. elif self.state in [WebSocketProtocol.STATE_PROXY_CONNECTING, WebSocketProtocol.STATE_CONNECTING]:
  1692. raise Exception("cannot close a connection not yet connected")
  1693. elif self.state == WebSocketProtocol.STATE_OPEN:
  1694. # construct Hybi close frame payload and send frame
  1695. payload = b''
  1696. if code is not None:
  1697. payload += struct.pack("!H", code)
  1698. if reasonUtf8 is not None:
  1699. payload += reasonUtf8
  1700. self.sendFrame(opcode=8, payload=payload)
  1701. # update state
  1702. self.state = WebSocketProtocol.STATE_CLOSING
  1703. self.closedByMe = not isReply
  1704. # remember payload of close frame we sent
  1705. self.localCloseCode = code
  1706. self.localCloseReason = reasonUtf8
  1707. # drop connection when timeout on receiving close handshake reply
  1708. if self.closedByMe and self.closeHandshakeTimeout > 0:
  1709. self.closeHandshakeTimeoutCall = self.factory._batched_timer.call_later(
  1710. self.closeHandshakeTimeout,
  1711. self.onCloseHandshakeTimeout,
  1712. )
  1713. else:
  1714. raise Exception("logic error")
  1715. def sendClose(self, code=None, reason=None):
  1716. """
  1717. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendClose`
  1718. """
  1719. if code is not None:
  1720. if type(code) != int:
  1721. raise Exception("invalid type '{}' for close code (must be an integer)".format(type(code)))
  1722. # 1000 Normal Closure
  1723. # 3000-3999 First Come First Served
  1724. # 4000-4999 Reserved for Private Use
  1725. # See: https://www.iana.org/assignments/websocket/websocket.xml#close-code-number-rules
  1726. #
  1727. if code != 1000 and not (3000 <= code <= 4999):
  1728. raise Exception("invalid close code {} (must be 1000 or from [3000, 4999])".format(code))
  1729. if reason is not None:
  1730. if code is None:
  1731. raise Exception("close reason without close code")
  1732. if type(reason) != str:
  1733. raise Exception("reason must be of type unicode (was '{}')".format(type(reason)))
  1734. reasonUtf8 = encode_truncate(reason, 123)
  1735. else:
  1736. reasonUtf8 = None
  1737. self.sendCloseFrame(code=code, reasonUtf8=reasonUtf8, isReply=False)
  1738. def beginMessage(self, isBinary=False, doNotCompress=False):
  1739. """
  1740. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.beginMessage`
  1741. """
  1742. if self.state != WebSocketProtocol.STATE_OPEN:
  1743. return
  1744. # check if sending state is valid for this method
  1745. #
  1746. if self.send_state != WebSocketProtocol.SEND_STATE_GROUND:
  1747. raise Exception("WebSocketProtocol.beginMessage invalid in current sending state")
  1748. self.send_message_opcode = WebSocketProtocol.MESSAGE_TYPE_BINARY if isBinary else WebSocketProtocol.MESSAGE_TYPE_TEXT
  1749. self.send_state = WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN
  1750. # setup compressor
  1751. #
  1752. if self._perMessageCompress is not None and not doNotCompress:
  1753. self.send_compressed = True
  1754. self._perMessageCompress.start_compress_message()
  1755. else:
  1756. self.send_compressed = False
  1757. self.trafficStats.outgoingWebSocketMessages += 1
  1758. def beginMessageFrame(self, length):
  1759. """
  1760. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.beginMessageFrame`
  1761. """
  1762. if self.state != WebSocketProtocol.STATE_OPEN:
  1763. return
  1764. # check if sending state is valid for this method
  1765. #
  1766. if self.send_state not in [WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN, WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE]:
  1767. raise Exception('WebSocketProtocol.beginMessageFrame invalid in current sending state [{}]'.format(self.send_state))
  1768. if type(length) != int or length < 0 or length > 0x7FFFFFFFFFFFFFFF: # 2**63
  1769. raise Exception('invalid value for message frame length')
  1770. self.send_message_frame_length = length
  1771. self.trafficStats.outgoingWebSocketFrames += 1
  1772. if (not self.factory.isServer and self.maskClientFrames) or (self.factory.isServer and self.maskServerFrames):
  1773. # automatic mask:
  1774. # - client-to-server masking (if not deactivated)
  1775. # - server-to-client masking (if activated)
  1776. #
  1777. # see note above about getrandbits
  1778. self.send_message_frame_mask = struct.pack("!I", random.getrandbits(32))
  1779. else:
  1780. # no mask
  1781. #
  1782. self.send_message_frame_mask = None
  1783. # payload masker
  1784. #
  1785. if self.send_message_frame_mask and length > 0 and self.applyMask:
  1786. self.send_message_frame_masker = create_xor_masker(self.send_message_frame_mask, length)
  1787. else:
  1788. self.send_message_frame_masker = XorMaskerNull()
  1789. # first byte
  1790. #
  1791. # FIN = false .. since with streaming, we don't know when message ends
  1792. b0 = 0
  1793. if self.send_state == WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN:
  1794. b0 |= self.send_message_opcode % 128
  1795. if self.send_compressed:
  1796. b0 |= (4 % 8) << 4
  1797. self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE
  1798. else:
  1799. pass # message continuation frame
  1800. # second byte, payload len bytes and mask
  1801. #
  1802. b1 = 0
  1803. if self.send_message_frame_mask:
  1804. b1 |= 1 << 7
  1805. mv = self.send_message_frame_mask
  1806. else:
  1807. mv = b''
  1808. el = b''
  1809. if length <= 125:
  1810. b1 |= length
  1811. elif length <= 0xFFFF:
  1812. b1 |= 126
  1813. el = struct.pack("!H", length)
  1814. elif length <= 0x7FFFFFFFFFFFFFFF:
  1815. b1 |= 127
  1816. el = struct.pack("!Q", length)
  1817. else:
  1818. raise Exception("invalid payload length")
  1819. # write message frame header
  1820. #
  1821. header = b''.join([b0.to_bytes(1, 'big'), b1.to_bytes(1, 'big'), el, mv])
  1822. self.sendData(header)
  1823. # now we are inside message frame ..
  1824. #
  1825. self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME
  1826. def sendMessageFrameData(self, payload, sync=False):
  1827. """
  1828. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessageFrameData`
  1829. """
  1830. if self.state != WebSocketProtocol.STATE_OPEN:
  1831. return
  1832. if not self.send_compressed:
  1833. self.trafficStats.outgoingOctetsAppLevel += len(payload)
  1834. self.trafficStats.outgoingOctetsWebSocketLevel += len(payload)
  1835. if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME:
  1836. raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state")
  1837. rl = len(payload)
  1838. if self.send_message_frame_masker.pointer() + rl > self.send_message_frame_length:
  1839. l = self.send_message_frame_length - self.send_message_frame_masker.pointer()
  1840. rest = -(rl - l)
  1841. pl = payload[:l]
  1842. else:
  1843. l = rl
  1844. rest = self.send_message_frame_length - self.send_message_frame_masker.pointer() - l
  1845. pl = payload
  1846. # mask frame payload
  1847. #
  1848. plm = self.send_message_frame_masker.process(pl)
  1849. # send frame payload
  1850. #
  1851. self.sendData(plm, sync=sync)
  1852. # if we are done with frame, move back into "inside message" state
  1853. #
  1854. if self.send_message_frame_masker.pointer() >= self.send_message_frame_length:
  1855. self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE
  1856. # when =0 : frame was completed exactly
  1857. # when >0 : frame is still incomplete and that much amount is still left to complete the frame
  1858. # when <0 : frame was completed and there was this much unconsumed data in payload argument
  1859. #
  1860. return rest
  1861. def endMessage(self):
  1862. """
  1863. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.endMessage`
  1864. """
  1865. if self.state != WebSocketProtocol.STATE_OPEN:
  1866. return
  1867. # check if sending state is valid for this method
  1868. #
  1869. # if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE:
  1870. # raise Exception('WebSocketProtocol.endMessage invalid in current sending state [{}]'.format(self.send_state))
  1871. if self.send_compressed:
  1872. payload = self._perMessageCompress.end_compress_message()
  1873. self.trafficStats.outgoingOctetsWebSocketLevel += len(payload)
  1874. else:
  1875. # send continuation frame with empty payload and FIN set to end message
  1876. payload = b''
  1877. self.sendFrame(opcode=0, payload=payload, fin=True)
  1878. self.send_state = WebSocketProtocol.SEND_STATE_GROUND
  1879. def sendMessageFrame(self, payload, sync=False):
  1880. """
  1881. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessageFrame`
  1882. """
  1883. if self.state != WebSocketProtocol.STATE_OPEN:
  1884. return
  1885. if self.send_compressed:
  1886. self.trafficStats.outgoingOctetsAppLevel += len(payload)
  1887. payload = self._perMessageCompress.compress_message_data(payload)
  1888. self.beginMessageFrame(len(payload))
  1889. self.sendMessageFrameData(payload, sync)
  1890. def sendMessage(self,
  1891. payload,
  1892. isBinary=False,
  1893. fragmentSize=None,
  1894. sync=False,
  1895. doNotCompress=False):
  1896. """
  1897. Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessage`
  1898. """
  1899. assert type(payload) == bytes, '"payload" must have type bytes, but was "{}"'.format(type(payload))
  1900. assert type(isBinary) == bool, '"isBinary" must have type bool, but was "{}"'.format(type(isBinary))
  1901. assert fragmentSize is None or type(fragmentSize) == int, '"fragmentSize" must have type int, but was "{}"'.format(type(fragmentSize))
  1902. assert type(sync) == bool, '"sync" must have type bool, but was "{}"'.format(type(sync))
  1903. assert type(doNotCompress) == bool, '"doNotCompress" must have type bool, but was "{}"'.format(type(doNotCompress))
  1904. if self.state != WebSocketProtocol.STATE_OPEN:
  1905. raise Disconnected("Attempt to send on a closed protocol")
  1906. if self.trackedTimings:
  1907. self.trackedTimings.track("sendMessage")
  1908. # (initial) frame opcode
  1909. #
  1910. if isBinary:
  1911. opcode = 2
  1912. else:
  1913. opcode = 1
  1914. self.trafficStats.outgoingWebSocketMessages += 1
  1915. # setup compressor
  1916. #
  1917. if self._perMessageCompress is not None and not doNotCompress:
  1918. sendCompressed = True
  1919. self._perMessageCompress.start_compress_message()
  1920. self.trafficStats.outgoingOctetsAppLevel += len(payload)
  1921. payload1 = self._perMessageCompress.compress_message_data(payload)
  1922. payload2 = self._perMessageCompress.end_compress_message()
  1923. payload = b''.join([payload1, payload2])
  1924. payload_len = len(payload)
  1925. self.trafficStats.outgoingOctetsWebSocketLevel += payload_len
  1926. else:
  1927. sendCompressed = False
  1928. payload_len = len(payload)
  1929. self.trafficStats.outgoingOctetsAppLevel += payload_len
  1930. self.trafficStats.outgoingOctetsWebSocketLevel += payload_len
  1931. if 0 < self.maxMessagePayloadSize < payload_len:
  1932. self.wasMaxMessagePayloadSizeExceeded = True
  1933. emsg = 'tried to send WebSocket message with size {} exceeding payload limit of {} octets'.format(payload_len, self.maxMessagePayloadSize)
  1934. self.log.warn(emsg)
  1935. raise PayloadExceededError(emsg)
  1936. # explicit fragmentSize arguments overrides autoFragmentSize setting
  1937. #
  1938. if fragmentSize is not None:
  1939. pfs = fragmentSize
  1940. else:
  1941. if self.autoFragmentSize > 0:
  1942. pfs = self.autoFragmentSize
  1943. else:
  1944. pfs = None
  1945. # send unfragmented
  1946. #
  1947. if pfs is None or len(payload) <= pfs:
  1948. self.sendFrame(opcode=opcode, payload=payload, sync=sync, rsv=4 if sendCompressed else 0)
  1949. # send data message in fragments
  1950. #
  1951. else:
  1952. if pfs < 1:
  1953. raise Exception('payload fragment size must be at least 1 (was {})'.format(pfs))
  1954. n = len(payload)
  1955. i = 0
  1956. done = False
  1957. first = True
  1958. while not done:
  1959. j = i + pfs
  1960. if j > n:
  1961. done = True
  1962. j = n
  1963. if first:
  1964. self.sendFrame(opcode=opcode, payload=payload[i:j], fin=done, sync=sync, rsv=4 if sendCompressed else 0)
  1965. first = False
  1966. else:
  1967. self.sendFrame(opcode=0, payload=payload[i:j], fin=done, sync=sync)
  1968. i += pfs
  1969. def _parseExtensionsHeader(self, header, removeQuotes=True):
  1970. """
  1971. Parse the Sec-WebSocket-Extensions header.
  1972. """
  1973. extensions = []
  1974. exts = [str(x.strip()) for x in header.split(',')]
  1975. for e in exts:
  1976. if e != "":
  1977. ext = [x.strip() for x in e.split(";")]
  1978. if len(ext) > 0:
  1979. extension = ext[0].lower()
  1980. params = {}
  1981. for p in ext[1:]:
  1982. p = [x.strip() for x in p.split("=")]
  1983. key = p[0].lower()
  1984. if len(p) > 1:
  1985. value = "=".join(p[1:])
  1986. if removeQuotes:
  1987. if len(value) > 0 and value[0] == '"':
  1988. value = value[1:]
  1989. if len(value) > 0 and value[-1] == '"':
  1990. value = value[:-1]
  1991. else:
  1992. value = True
  1993. if key not in params:
  1994. params[key] = []
  1995. params[key].append(value)
  1996. extensions.append((extension, params))
  1997. else:
  1998. pass # should not arrive here
  1999. return extensions
  2000. IWebSocketChannel.register(WebSocketProtocol)
  2001. IWebSocketChannelFrameApi.register(WebSocketProtocol)
  2002. IWebSocketChannelStreamingApi.register(WebSocketProtocol)
  2003. class PreparedMessage(object):
  2004. """
  2005. Encapsulates a prepared message to be sent later once or multiple
  2006. times on one or more WebSocket connections.
  2007. This can be used for optimizing Broadcast/PubSub.
  2008. """
  2009. def __init__(self, payload, isBinary, applyMask, doNotCompress):
  2010. """
  2011. Ctor for a prepared message.
  2012. :param payload: The message payload.
  2013. :type payload: str
  2014. :param isBinary: Provide `True` for binary payload.
  2015. :type isBinary: bool
  2016. :param applyMask: Provide `True` if WebSocket message is to be masked (required for client to server WebSocket messages).
  2017. :type applyMask: bool
  2018. :param doNotCompress: Iff `True`, never compress this message. This
  2019. only applies when WebSocket compression has been negotiated on the
  2020. WebSocket connection. Use when you know the payload incompressible
  2021. (e.g. encrypted or already compressed).
  2022. :type doNotCompress: bool
  2023. """
  2024. if not doNotCompress:
  2025. # we need to store original payload for compressed WS
  2026. # connections (cannot compress/frame in advanced when
  2027. # compression is on, and context takeover is off)
  2028. self.payload = payload
  2029. self.binary = isBinary
  2030. self.doNotCompress = doNotCompress
  2031. l = len(payload)
  2032. # first byte
  2033. #
  2034. b0 = ((1 << 7) | 2) if isBinary else ((1 << 7) | 1)
  2035. # second byte, payload len bytes and mask
  2036. #
  2037. if applyMask:
  2038. b1 = 1 << 7
  2039. # see note above about getrandbits
  2040. mask = struct.pack("!I", random.getrandbits(32))
  2041. if l == 0:
  2042. plm = payload
  2043. else:
  2044. plm = create_xor_masker(mask, l).process(payload)
  2045. else:
  2046. b1 = 0
  2047. mask = b''
  2048. plm = payload
  2049. # payload extended length
  2050. #
  2051. el = b''
  2052. if l <= 125:
  2053. b1 |= l
  2054. elif l <= 0xFFFF:
  2055. b1 |= 126
  2056. el = struct.pack("!H", l)
  2057. elif l <= 0x7FFFFFFFFFFFFFFF:
  2058. b1 |= 127
  2059. el = struct.pack("!Q", l)
  2060. else:
  2061. raise Exception("invalid payload length")
  2062. # raw WS message (single frame)
  2063. #
  2064. self.payloadHybi = b''.join([b0.to_bytes(1, 'big'), b1.to_bytes(1, 'big'), el, mask, plm])
  2065. class WebSocketFactory(object):
  2066. """
  2067. Mixin for
  2068. :class:`autobahn.websocket.protocol.WebSocketClientFactory` and
  2069. :class:`autobahn.websocket.protocol.WebSocketServerFactory`.
  2070. """
  2071. def prepareMessage(self, payload, isBinary=False, doNotCompress=False):
  2072. """
  2073. Prepare a WebSocket message. This can be later sent on multiple
  2074. instances of :class:`autobahn.websocket.WebSocketProtocol` using
  2075. :meth:`autobahn.websocket.WebSocketProtocol.sendPreparedMessage`.
  2076. By doing so, you can avoid the (small) overhead of framing the
  2077. *same* payload into WebSocket messages multiple times when that
  2078. same payload is to be sent out on multiple connections.
  2079. :param payload: The message payload.
  2080. :type payload: bytes
  2081. :param isBinary: `True` iff payload is binary, else the payload must be
  2082. UTF-8 encoded text.
  2083. :type isBinary: bool
  2084. :param doNotCompress: Iff `True`, never compress this message. This
  2085. only applies when WebSocket compression has been negotiated on the
  2086. WebSocket connection. Use when you know the payload incompressible
  2087. (e.g. encrypted or already compressed).
  2088. :type doNotCompress: bool
  2089. :returns: An instance of :class:`autobahn.websocket.protocol.PreparedMessage`.
  2090. """
  2091. applyMask = not self.isServer
  2092. return PreparedMessage(payload, isBinary, applyMask, doNotCompress)
  2093. _SERVER_STATUS_TEMPLATE = """<!DOCTYPE html>
  2094. <html>
  2095. <head>
  2096. {}
  2097. <style>
  2098. body {{
  2099. color: #fff;
  2100. background-color: #027eae;
  2101. font-family: "Segoe UI", "Lucida Grande", "Helvetica Neue", Helvetica, Arial, sans-serif;
  2102. font-size: 16px;
  2103. }}
  2104. a, a:visited, a:hover {{
  2105. color: #fff;
  2106. }}
  2107. </style>
  2108. </head>
  2109. <body>
  2110. <h1>AutobahnPython {}</h1>
  2111. <p>
  2112. I am not Web server, but a <b>WebSocket Endpoint</b>.
  2113. </p>
  2114. <p>
  2115. You can talk to me using the <a href="http://tools.ietf.org/html/rfc6455">WebSocket</a> protocol.
  2116. </p>
  2117. <p>
  2118. For more information, please see:
  2119. <ul>
  2120. <li><a href="http://crossbar.io/autobahn">Autobahn</a></li>
  2121. </ul>
  2122. </p>
  2123. </body>
  2124. </html>
  2125. """
  2126. class WebSocketServerProtocol(WebSocketProtocol):
  2127. """
  2128. Protocol base class for WebSocket servers.
  2129. """
  2130. CONFIG_ATTRS = WebSocketProtocol.CONFIG_ATTRS_COMMON + WebSocketProtocol.CONFIG_ATTRS_SERVER
  2131. def onConnect(self, request: ConnectionRequest) -> Union[Optional[str], Tuple[Optional[str], Dict[str, str]]]:
  2132. """
  2133. Callback fired during WebSocket opening handshake when new WebSocket client
  2134. connection is about to be established.
  2135. When you want to accept the connection, return the accepted protocol
  2136. from list of WebSocket (sub)protocols provided by client or `None` to
  2137. speak no specific one or when the client protocol list was empty.
  2138. You may also return a pair of `(protocol, headers)` to send additional
  2139. HTTP headers, with `headers` being a dictionary of key-values.
  2140. Throw :class:`autobahn.websocket.types.ConnectionDeny` when you don't want
  2141. to accept the WebSocket connection request.
  2142. :param request: WebSocket connection request information.
  2143. :returns: You may return one of: ``None``: the connection is accepted with no specific WebSocket subprotocol,
  2144. ``str``: the connection is accepted with the returned name as the WebSocket subprotocol, or
  2145. ``(str, dict)``: a pair of subprotocol accepted and HTTP headers to send to the client.
  2146. You can also return a Deferred/Future that resolves/rejects to the above.
  2147. """
  2148. self.log.debug('{func}: request={request}', func=hltype(self.onConnect), request=request)
  2149. return None
  2150. def _connectionMade(self):
  2151. """
  2152. Called by network framework when new transport connection from client was
  2153. accepted. Default implementation will prepare for initial WebSocket opening
  2154. handshake. When overriding in derived class, make sure to call this base class
  2155. implementation *before* your code.
  2156. """
  2157. self.log.debug('{func}: connection accepted from peer {peer}',
  2158. func=hltype(self._connectionMade), peer=self.peer)
  2159. WebSocketProtocol._connectionMade(self)
  2160. self.factory.countConnections += 1
  2161. def _connectionLost(self, reason):
  2162. """
  2163. Called by network framework when established transport connection from client
  2164. was lost. Default implementation will tear down all state properly.
  2165. When overriding in derived class, make sure to call this base class
  2166. implementation *after* your code.
  2167. """
  2168. self.log.debug('{func}: connection lost to peer {peer}: reason={reason}',
  2169. func=hltype(self._connectionLost), peer=self.peer, reason=hlval(reason))
  2170. WebSocketProtocol._connectionLost(self, reason)
  2171. self.factory.countConnections -= 1
  2172. def processProxyConnect(self):
  2173. raise Exception("Autobahn isn't a proxy server")
  2174. def processHandshake(self):
  2175. """
  2176. Process WebSocket opening handshake request from client.
  2177. """
  2178. # only proceed when we have fully received the HTTP request line and all headers
  2179. #
  2180. end_of_header = self.data.find(b"\x0d\x0a\x0d\x0a")
  2181. if end_of_header >= 0:
  2182. self.log.debug('{func} found end of HTTP request header at byte {end_of_header}',
  2183. func=hltype(self.processHandshake), end_of_header=hlval(end_of_header))
  2184. self.http_request_data = self.data[:end_of_header + 4]
  2185. self.log.debug(
  2186. "received HTTP request:\n\n{data}\n\n",
  2187. data=self.http_request_data,
  2188. )
  2189. # extract HTTP status line and headers
  2190. #
  2191. try:
  2192. self.http_status_line, self.http_headers, http_headers_cnt = parseHttpHeader(self.http_request_data)
  2193. except Exception as e:
  2194. return self.failHandshake("Error during parsing of HTTP status line / request headers : {0}".format(e))
  2195. # replace self.peer if the x-forwarded-for header is present and trusted
  2196. #
  2197. if 'x-forwarded-for' in self.http_headers and self.trustXForwardedFor:
  2198. addresses = [x.strip() for x in self.http_headers['x-forwarded-for'].split(',')]
  2199. trusted_addresses = addresses[-self.trustXForwardedFor:]
  2200. self.peer = trusted_addresses[0]
  2201. # validate WebSocket opening handshake client request
  2202. #
  2203. self.log.debug(
  2204. "received HTTP status line in opening handshake : {status}",
  2205. status=self.http_status_line,
  2206. )
  2207. self.log.debug(
  2208. "received HTTP headers in opening handshake : {headers}",
  2209. headers=self.http_headers,
  2210. )
  2211. # HTTP Request line : METHOD, VERSION
  2212. #
  2213. rl = self.http_status_line.split()
  2214. if len(rl) != 3:
  2215. return self.failHandshake('Bad HTTP request status line "{}"'.format(self.http_status_line))
  2216. if rl[0].strip() != "GET":
  2217. return self.failHandshake('HTTP method "{}" not allowed'.format(rl[0]), 405)
  2218. vs = rl[2].strip().split("/")
  2219. if len(vs) != 2 or vs[0] != "HTTP" or vs[1] not in ["1.1"]:
  2220. return self.failHandshake('Unsupported HTTP version "{}"'.format(rl[2]), 505)
  2221. # HTTP Request line : REQUEST-URI
  2222. #
  2223. self.http_request_uri = rl[1].strip()
  2224. try:
  2225. (scheme, netloc, path, params, query, fragment) = parse.urlparse(self.http_request_uri)
  2226. # FIXME: check that if absolute resource URI is given,
  2227. # the scheme/netloc matches the server
  2228. if scheme != "" or netloc != "":
  2229. pass
  2230. # Fragment identifiers are meaningless in the context of WebSocket
  2231. # URIs, and MUST NOT be used on these URIs.
  2232. if fragment != "":
  2233. return self.failHandshake('HTTP requested resource contains a fragment identifier "{}"'.format(fragment))
  2234. # resource path and query parameters .. this will get forwarded
  2235. # to onConnect()
  2236. self.http_request_path = path
  2237. self.http_request_params = parse.parse_qs(query)
  2238. except:
  2239. return self.failHandshake('Bad HTTP request resource - could not parse "{}"'.format(rl[1].strip()))
  2240. # Host
  2241. #
  2242. if 'host' not in self.http_headers:
  2243. return self.failHandshake('HTTP Host header missing in opening handshake request')
  2244. if http_headers_cnt["host"] > 1:
  2245. return self.failHandshake('HTTP Host header appears more than once in opening handshake request')
  2246. self.http_request_host = self.http_headers["host"].strip()
  2247. if self.http_request_host.find(":") >= 0 and not self.http_request_host.endswith(']'):
  2248. (h, p) = self.http_request_host.rsplit(":", 1)
  2249. try:
  2250. port = int(str(p.strip()))
  2251. except ValueError:
  2252. return self.failHandshake('invalid port "{}" in HTTP Host header "{}"'.format(str(p.strip()),
  2253. str(self.http_request_host)))
  2254. # do port checking only if externalPort was set
  2255. if self.factory.externalPort:
  2256. if port != self.factory.externalPort:
  2257. return self.failHandshake('port {} in HTTP Host header "{}" does not match server listening port {}'.format(port, str(self.http_request_host), self.factory.externalPort))
  2258. else:
  2259. self.log.debug("skipping opening handshake port checking - neither WS URL nor external port set")
  2260. self.http_request_host = h
  2261. # Upgrade
  2262. #
  2263. if 'upgrade' not in self.http_headers:
  2264. # When no WS upgrade, render HTML server status page
  2265. #
  2266. if self.webStatus:
  2267. if 'redirect' in self.http_request_params and len(self.http_request_params['redirect']) > 0:
  2268. # To specify an URL for redirection, encode the URL, i.e. from JavaScript:
  2269. #
  2270. # var url = encodeURIComponent("http://crossbar.io/autobahn");
  2271. #
  2272. # and append the encoded string as a query parameter 'redirect'
  2273. #
  2274. # http://localhost:9000?redirect=http%3A%2F%2Fcrossbar.io%2Fautobahn
  2275. # https://localhost:9000?redirect=https%3A%2F%2Ftwitter.com%2F
  2276. #
  2277. # This will perform an immediate HTTP-303 redirection. If you provide
  2278. # an additional parameter 'after' (int >= 0), the redirection happens
  2279. # via Meta-Refresh in the rendered HTML status page, i.e.
  2280. #
  2281. # https://localhost:9000/?redirect=https%3A%2F%2Ftwitter.com%2F&after=3
  2282. #
  2283. url = hyperlink.URL.from_text(self.http_request_params['redirect'][0])
  2284. url = url.to_uri().normalize().to_text()
  2285. if 'after' in self.http_request_params and len(self.http_request_params['after']) > 0:
  2286. after = int(self.http_request_params['after'][0])
  2287. self.log.debug(
  2288. "HTTP Upgrade header missing : render server status page and "
  2289. "meta-refresh-redirecting to {url} after {duration} seconds",
  2290. url=url,
  2291. duration=after,
  2292. )
  2293. self.sendServerStatus(url, after)
  2294. else:
  2295. self.log.debug(
  2296. "HTTP Upgrade header missing : 303-redirecting to {url}",
  2297. url=url,
  2298. )
  2299. self.sendRedirect(url)
  2300. else:
  2301. self.log.debug("HTTP Upgrade header missing : render server status page")
  2302. self.sendServerStatus()
  2303. self.dropConnection(abort=False)
  2304. return
  2305. else:
  2306. return self.failHandshake("HTTP Upgrade header missing", 426) # Upgrade Required
  2307. upgradeWebSocket = False
  2308. for u in self.http_headers["upgrade"].split(","):
  2309. if u.strip().lower() == "websocket":
  2310. upgradeWebSocket = True
  2311. break
  2312. if not upgradeWebSocket:
  2313. return self.failHandshake('HTTP Upgrade headers do not include "websocket" value (case-insensitive): "{}"'.format(self.http_headers["upgrade"]))
  2314. # Connection
  2315. #
  2316. if 'connection' not in self.http_headers:
  2317. return self.failHandshake("HTTP Connection header missing")
  2318. connectionUpgrade = False
  2319. for c in self.http_headers["connection"].split(","):
  2320. if c.strip().lower() == "upgrade":
  2321. connectionUpgrade = True
  2322. break
  2323. if not connectionUpgrade:
  2324. return self.failHandshake('HTTP Connection headers do not include "upgrade" value (case-insensitive): "{}"'.format(self.http_headers["connection"]))
  2325. # Sec-WebSocket-Version PLUS determine mode: Hybi or Hixie
  2326. #
  2327. if 'sec-websocket-version' not in self.http_headers:
  2328. self.log.debug("Hixie76 protocol detected")
  2329. return self.failHandshake("WebSocket connection denied - Hixie76 protocol not supported.")
  2330. else:
  2331. self.log.debug("Hybi protocol detected")
  2332. if http_headers_cnt["sec-websocket-version"] > 1:
  2333. return self.failHandshake("HTTP Sec-WebSocket-Version header appears more than once in opening handshake request")
  2334. try:
  2335. version = int(self.http_headers["sec-websocket-version"])
  2336. except ValueError:
  2337. return self.failHandshake('could not parse HTTP Sec-WebSocket-Version header "{}" in opening handshake request'.format(self.http_headers["sec-websocket-version"]))
  2338. if version not in self.versions:
  2339. # respond with list of supported versions (descending order)
  2340. #
  2341. sv = sorted(self.versions)
  2342. sv.reverse()
  2343. svs = ','.join([str(x) for x in sv])
  2344. return self.failHandshake('WebSocket version {} not supported (supported versions: {})'.format(version, svs),
  2345. 400, # Bad Request
  2346. [("Sec-WebSocket-Version", svs)])
  2347. else:
  2348. # store the protocol version we are supposed to talk
  2349. self.websocket_version = version
  2350. # Sec-WebSocket-Protocol
  2351. #
  2352. if 'sec-websocket-protocol' in self.http_headers:
  2353. protocols = [str(x.strip()) for x in self.http_headers["sec-websocket-protocol"].split(",")]
  2354. # check for duplicates in protocol header
  2355. pp = {}
  2356. for p in protocols:
  2357. if p in pp:
  2358. return self.failHandshake('duplicate protocol "{}" specified in HTTP Sec-WebSocket-Protocol header'.format(p))
  2359. else:
  2360. pp[p] = 1
  2361. # ok, no duplicates, save list in order the client sent it
  2362. self.websocket_protocols = protocols
  2363. else:
  2364. self.websocket_protocols = []
  2365. # Origin / Sec-WebSocket-Origin
  2366. # http://tools.ietf.org/html/draft-ietf-websec-origin-02
  2367. #
  2368. if self.websocket_version < 13:
  2369. # Hybi, but only < Hybi-13
  2370. websocket_origin_header_key = 'sec-websocket-origin'
  2371. else:
  2372. # RFC6455, >= Hybi-13
  2373. websocket_origin_header_key = "origin"
  2374. self.websocket_origin = ""
  2375. if websocket_origin_header_key in self.http_headers:
  2376. if http_headers_cnt[websocket_origin_header_key] > 1:
  2377. return self.failHandshake("HTTP Origin header appears more than once in opening handshake request")
  2378. self.websocket_origin = self.http_headers[websocket_origin_header_key].strip()
  2379. try:
  2380. origin_tuple = _url_to_origin(self.websocket_origin)
  2381. except ValueError as e:
  2382. return self.failHandshake(
  2383. "HTTP Origin header invalid: {}".format(e)
  2384. )
  2385. have_origin = True
  2386. else:
  2387. # non-browser clients are allowed to omit this header
  2388. have_origin = False
  2389. if have_origin:
  2390. if origin_tuple == 'null' and self.factory.allowNullOrigin:
  2391. origin_is_allowed = True
  2392. else:
  2393. origin_is_allowed = _is_same_origin(
  2394. origin_tuple,
  2395. 'https' if self.factory.isSecure else 'http',
  2396. self.factory.externalPort or self.factory.port,
  2397. self.allowedOriginsPatterns,
  2398. )
  2399. if not origin_is_allowed:
  2400. return self.failHandshake(
  2401. "WebSocket connection denied: origin '{0}' "
  2402. "not allowed".format(self.websocket_origin)
  2403. )
  2404. # Sec-WebSocket-Key
  2405. #
  2406. if 'sec-websocket-key' not in self.http_headers:
  2407. return self.failHandshake("HTTP Sec-WebSocket-Key header missing")
  2408. if http_headers_cnt["sec-websocket-key"] > 1:
  2409. return self.failHandshake("HTTP Sec-WebSocket-Key header appears more than once in opening handshake request")
  2410. key = self.http_headers["sec-websocket-key"].strip()
  2411. if len(key) != 24: # 16 bytes => (ceil(128/24)*24)/6 == 24
  2412. return self.failHandshake('bad Sec-WebSocket-Key (length must be 24 ASCII chars) "{}"'.format(key))
  2413. if key[-2:] != "==": # 24 - ceil(128/6) == 2
  2414. return self.failHandshake('bad Sec-WebSocket-Key (invalid base64 encoding) "{}"'.format(key))
  2415. for c in key[:-2]:
  2416. if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/":
  2417. return self.failHandshake('bad character "{}" in Sec-WebSocket-Key (invalid base64 encoding) "{}"'.format(c, key))
  2418. # Sec-WebSocket-Extensions
  2419. #
  2420. self.websocket_extensions = []
  2421. if 'sec-websocket-extensions' in self.http_headers:
  2422. if http_headers_cnt["sec-websocket-extensions"] > 1:
  2423. return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake request")
  2424. else:
  2425. # extensions requested/offered by client
  2426. #
  2427. self.websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"])
  2428. # Ok, got complete HS input, remember rest (if any)
  2429. #
  2430. self.data = self.data[end_of_header + 4:]
  2431. # store WS key
  2432. #
  2433. # noinspection PyUnboundLocalVariable
  2434. self._wskey = key
  2435. # DoS protection
  2436. #
  2437. if self.maxConnections > 0 and self.factory.countConnections > self.maxConnections:
  2438. # maximum number of concurrent connections reached
  2439. #
  2440. self.failHandshake("maximum number of connections reached", code=503) # Service Unavailable
  2441. else:
  2442. # WebSocket handshake validated => produce opening handshake response
  2443. #
  2444. request = ConnectionRequest(self.peer,
  2445. self.http_headers,
  2446. self.http_request_host,
  2447. self.http_request_path,
  2448. self.http_request_params,
  2449. self.websocket_version,
  2450. self.websocket_origin,
  2451. self.websocket_protocols,
  2452. self.websocket_extensions)
  2453. # The user's onConnect() handler must do one of the following:
  2454. # - return the subprotocol to be spoken
  2455. # - return None to continue with no subprotocol
  2456. # - return a pair (subprotocol, headers)
  2457. # - raise a ConnectionDeny to dismiss the client
  2458. f = txaio.as_future(self.onConnect, request)
  2459. def forward_error(err):
  2460. if isinstance(err.value, ConnectionDeny):
  2461. # the user handler explicitly denies the connection
  2462. self.failHandshake(err.value.reason, err.value.code)
  2463. else:
  2464. # the user handler ran into an unexpected error (and hence, user code needs fixing!)
  2465. self.log.warn("Unexpected exception in onConnect ['{err.value}']", err=err)
  2466. self.log.warn("{tb}", tb=txaio.failure_format_traceback(err))
  2467. return self.failHandshake("Internal server error: {}".format(err.value), ConnectionDeny.INTERNAL_SERVER_ERROR)
  2468. txaio.add_callbacks(f, self.succeedHandshake, forward_error)
  2469. elif self.serveFlashSocketPolicy:
  2470. flash_policy_file_request = self.data.find(b"<policy-file-request/>\x00")
  2471. if flash_policy_file_request >= 0:
  2472. self.log.debug("received Flash Socket Policy File request")
  2473. if self.serveFlashSocketPolicy:
  2474. self.log.debug(
  2475. "sending Flash Socket Policy File :\n{policy}",
  2476. policy=self.flashSocketPolicy,
  2477. )
  2478. self.sendData(self.flashSocketPolicy.encode('utf8'))
  2479. self.wasServingFlashSocketPolicyFile = True
  2480. self.dropConnection()
  2481. else:
  2482. self.log.debug(
  2483. "No Flash Policy File served. You might want to serve a"
  2484. " Flask Socket Policy file on the destination port "
  2485. "since you received a request for it. See "
  2486. "WebSocketServerFactory.serveFlashSocketPolicy and "
  2487. "WebSocketServerFactory.flashSocketPolicy"
  2488. )
  2489. def succeedHandshake(self, res):
  2490. """
  2491. Callback after onConnect() returns successfully. Generates the response for the handshake.
  2492. """
  2493. protocol = None
  2494. headers = {}
  2495. if type(res) == tuple:
  2496. if len(res) > 0:
  2497. protocol = res[0]
  2498. if len(res) > 1:
  2499. headers = res[1]
  2500. else:
  2501. protocol = res
  2502. if protocol is not None and not (protocol in self.websocket_protocols):
  2503. raise Exception("protocol accepted must be from the list client sent or None")
  2504. self.websocket_protocol_in_use = protocol
  2505. key = self._wskey
  2506. # extensions effectively in use for this connection
  2507. #
  2508. self.websocket_extensions_in_use = []
  2509. extensionResponse = []
  2510. # gets filled with permessage-compress offers from the client
  2511. #
  2512. pmceOffers = []
  2513. # handle WebSocket extensions
  2514. #
  2515. for (extension, params) in self.websocket_extensions:
  2516. self.log.debug(
  2517. "parsed WebSocket extension '{extension}' with params '{params}'",
  2518. extension=extension,
  2519. params=params,
  2520. )
  2521. # process permessage-compress extension
  2522. #
  2523. if extension in PERMESSAGE_COMPRESSION_EXTENSION:
  2524. PMCE = PERMESSAGE_COMPRESSION_EXTENSION[extension]
  2525. try:
  2526. offer = PMCE['Offer'].parse(params)
  2527. pmceOffers.append(offer)
  2528. except Exception as e:
  2529. return self.failHandshake(str(e))
  2530. else:
  2531. self.log.debug(
  2532. "client requested '{extension}' extension we don't support "
  2533. "or which is not activated",
  2534. extension=extension,
  2535. )
  2536. # handle permessage-compress offers by the client
  2537. #
  2538. if len(pmceOffers) > 0:
  2539. accept = self.perMessageCompressionAccept(pmceOffers)
  2540. if accept is not None:
  2541. PMCE = PERMESSAGE_COMPRESSION_EXTENSION[accept.EXTENSION_NAME]
  2542. self._perMessageCompress = PMCE['PMCE'].create_from_offer_accept(self.factory.isServer, accept)
  2543. self.websocket_extensions_in_use.append(self._perMessageCompress)
  2544. extensionResponse.append(accept.get_extension_string())
  2545. else:
  2546. self.log.debug(
  2547. "client request permessage-compress extension, but we did "
  2548. "not accept any offer [{offers}]",
  2549. offers=pmceOffers,
  2550. )
  2551. # build response to complete WebSocket handshake
  2552. #
  2553. response = "HTTP/1.1 101 Switching Protocols\x0d\x0a"
  2554. if self.factory.server:
  2555. response += 'Server: {}\x0d\x0a'.format(self.factory.server)
  2556. response += "Upgrade: WebSocket\x0d\x0a"
  2557. response += "Connection: Upgrade\x0d\x0a"
  2558. # optional, user supplied additional HTTP headers
  2559. #
  2560. # headers from factory, headers from onConnect
  2561. for headers_source in (self.factory.headers.items(), headers.items()):
  2562. for uh in headers_source:
  2563. if isinstance(uh[1], (str, )):
  2564. header_values = [uh[1]]
  2565. else:
  2566. try:
  2567. header_values = iter(uh[1])
  2568. except TypeError:
  2569. header_values = [uh[1]]
  2570. for header_value in header_values:
  2571. response += '{}: {}\x0d\x0a'.format(uh[0], header_value)
  2572. if self.websocket_protocol_in_use is not None:
  2573. response += 'Sec-WebSocket-Protocol: {}\x0d\x0a'.format(self.websocket_protocol_in_use)
  2574. # compute Sec-WebSocket-Accept
  2575. #
  2576. sha1 = hashlib.sha1()
  2577. # noinspection PyUnboundLocalVariable
  2578. sha1.update(key.encode('utf8') + WebSocketProtocol._WS_MAGIC)
  2579. sec_websocket_accept = base64.b64encode(sha1.digest())
  2580. response += 'Sec-WebSocket-Accept: {}\x0d\x0a'.format(sec_websocket_accept.decode())
  2581. # agreed extensions
  2582. #
  2583. if len(extensionResponse) > 0:
  2584. response += 'Sec-WebSocket-Extensions: {}\x0d\x0a'.format(','.join(extensionResponse))
  2585. # end of HTTP response headers
  2586. response += "\x0d\x0a"
  2587. response_body = None
  2588. # send out opening handshake response
  2589. #
  2590. self.log.debug("sending HTTP response:\n\n{response}", response=response)
  2591. self.sendData(response.encode('utf8'))
  2592. if response_body:
  2593. self.log.debug(
  2594. "sending HTTP response body:\n\n{octets}",
  2595. octets=_LazyHexFormatter(response_body),
  2596. )
  2597. self.sendData(response_body)
  2598. # save response for testsuite
  2599. #
  2600. self.http_response_data = response
  2601. # opening handshake completed, move WebSocket connection into OPEN state
  2602. #
  2603. self.state = WebSocketProtocol.STATE_OPEN
  2604. # cancel any opening HS timer if present
  2605. #
  2606. if self.openHandshakeTimeoutCall is not None:
  2607. self.log.debug("openHandshakeTimeoutCall.cancel")
  2608. self.openHandshakeTimeoutCall.cancel()
  2609. self.openHandshakeTimeoutCall = None
  2610. # init state
  2611. #
  2612. self.inside_message = False
  2613. self.current_frame = None
  2614. # automatic ping/pong
  2615. #
  2616. if self.autoPingInterval:
  2617. self.autoPingPendingCall = self.factory._batched_timer.call_later(
  2618. self.autoPingInterval,
  2619. self._sendAutoPing,
  2620. )
  2621. # fire handler on derived class
  2622. #
  2623. if self.trackedTimings:
  2624. self.trackedTimings.track("onOpen")
  2625. self._onOpen()
  2626. txaio.resolve(self.is_open, None)
  2627. # process rest, if any
  2628. #
  2629. if len(self.data) > 0:
  2630. self.consumeData()
  2631. def failHandshake(self, reason, code=400, responseHeaders=None):
  2632. """
  2633. During opening handshake the client request was invalid, we send a HTTP
  2634. error response and then drop the connection.
  2635. """
  2636. self.wasNotCleanReason = reason
  2637. self.log.info("failing WebSocket opening handshake ('{reason}')", reason=reason)
  2638. self.sendHttpErrorResponse(code, reason, responseHeaders)
  2639. self.dropConnection(abort=False)
  2640. def sendHttpErrorResponse(self, code, reason, responseHeaders=None):
  2641. """
  2642. Send out HTTP error response.
  2643. """
  2644. response = "HTTP/1.1 {0} {1}\x0d\x0a".format(code, reason)
  2645. if responseHeaders:
  2646. for h in responseHeaders:
  2647. response += "{0}: {1}\x0d\x0a".format(h[0], h[1])
  2648. response += "\x0d\x0a"
  2649. self.sendData(response.encode('utf8'))
  2650. def sendHtml(self, html):
  2651. """
  2652. Send HTML page HTTP response.
  2653. """
  2654. responseBody = html.encode('utf8')
  2655. response = "HTTP/1.1 200 OK\x0d\x0a"
  2656. if self.factory.server is not None and self.factory.server != "":
  2657. response += 'Server: {}\x0d\x0a'.format(self.factory.server)
  2658. response += 'Content-Type: text/html; charset=UTF-8\x0d\x0a'
  2659. response += 'Content-Length: {}\x0d\x0a'.format(len(responseBody))
  2660. response += '\x0d\x0a'
  2661. self.sendData(response.encode('utf8'))
  2662. self.sendData(responseBody)
  2663. def sendRedirect(self, url):
  2664. """
  2665. Send HTTP Redirect (303) response.
  2666. """
  2667. response = "HTTP/1.1 303\x0d\x0a"
  2668. if self.factory.server is not None and self.factory.server != "":
  2669. response += 'Server: {}\x0d\x0a'.format(self.factory.server)
  2670. response += 'Location: {}\x0d\x0a'.format(url)
  2671. response += '\x0d\x0a'
  2672. self.sendData(response.encode('utf8'))
  2673. def sendServerStatus(self, redirectUrl=None, redirectAfter=0):
  2674. """
  2675. Used to send out server status/version upon receiving a HTTP/GET without
  2676. upgrade to WebSocket header (and option serverStatus is True).
  2677. """
  2678. if redirectUrl:
  2679. redirect = """<meta http-equiv="refresh" content="{};URL='{}'">""".format(redirectAfter, redirectUrl)
  2680. else:
  2681. redirect = ""
  2682. self.sendHtml(_SERVER_STATUS_TEMPLATE.format(redirect, __version__))
  2683. class WebSocketServerFactory(WebSocketFactory):
  2684. """
  2685. A protocol factory for WebSocket servers.
  2686. Implements :func:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
  2687. """
  2688. protocol = WebSocketServerProtocol
  2689. """
  2690. The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketServerProtocol`.
  2691. """
  2692. isServer = True
  2693. """
  2694. Flag indicating if this factory is client- or server-side.
  2695. """
  2696. def __init__(self,
  2697. url=None,
  2698. protocols=None,
  2699. server='AutobahnPython/{}'.format(__version__),
  2700. headers=None,
  2701. externalPort=None):
  2702. """
  2703. Implements :func:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory.__init__`
  2704. """
  2705. self.logOctets = False
  2706. self.logFrames = False
  2707. self.trackTimings = False
  2708. # batch up and chunk timers ("call_later")
  2709. self._batched_timer = txaio.make_batched_timer(
  2710. bucket_seconds=0.200,
  2711. chunk_size=1000,
  2712. )
  2713. # seed RNG which is used for WS frame masks generation
  2714. random.seed()
  2715. # default WS session parameters
  2716. #
  2717. self.setSessionParameters(url, protocols, server, headers, externalPort)
  2718. # default WebSocket protocol options
  2719. #
  2720. self.resetProtocolOptions()
  2721. # number of currently connected clients
  2722. #
  2723. self.countConnections = 0
  2724. def setSessionParameters(self,
  2725. url=None,
  2726. protocols=None,
  2727. server=None,
  2728. headers=None,
  2729. externalPort=None):
  2730. """
  2731. Implements :func:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory.setSessionParameters`
  2732. """
  2733. # parse WebSocket URI into components
  2734. (isSecure, host, port, resource, path, params) = parse_url(url or "ws://localhost")
  2735. if len(params) > 0:
  2736. raise Exception("query parameters specified for server WebSocket URL")
  2737. self.url = url
  2738. self.isSecure = isSecure
  2739. self.host = host
  2740. self.port = port
  2741. self.resource = resource
  2742. self.path = path
  2743. self.params = params
  2744. self.protocols = protocols or []
  2745. self.server = server
  2746. self.headers = headers or {}
  2747. self.externalPort = externalPort
  2748. def resetProtocolOptions(self):
  2749. """
  2750. Implements :func:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory.resetProtocolOptions`
  2751. """
  2752. self.versions = WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS
  2753. self.webStatus = True
  2754. self.utf8validateIncoming = True
  2755. self.requireMaskedClientFrames = True
  2756. self.maskServerFrames = False
  2757. self.applyMask = True
  2758. self.maxFramePayloadSize = 0
  2759. self.maxMessagePayloadSize = 0
  2760. self.autoFragmentSize = 0
  2761. self.failByDrop = True
  2762. self.echoCloseCodeReason = False
  2763. self.openHandshakeTimeout = 5
  2764. self.closeHandshakeTimeout = 1
  2765. self.tcpNoDelay = True
  2766. self.serveFlashSocketPolicy = False
  2767. self.flashSocketPolicy = '''<cross-domain-policy>
  2768. <allow-access-from domain="*" to-ports="*" />
  2769. </cross-domain-policy>\x00'''
  2770. # permessage-XXX extension
  2771. #
  2772. self.perMessageCompressionAccept = lambda _: None
  2773. # automatic ping/pong ("heartbeating")
  2774. #
  2775. self.autoPingInterval = 0
  2776. self.autoPingTimeout = 0
  2777. self.autoPingSize = 12
  2778. # see: https://github.com/crossbario/autobahn-python/issues/1327 and _cancelAutoPingTimeoutCall
  2779. self.autoPingRestartOnAnyTraffic = True
  2780. # check WebSocket origin against this list
  2781. self.allowedOrigins = ["*"]
  2782. self.allowedOriginsPatterns = wildcards2patterns(self.allowedOrigins)
  2783. self.allowNullOrigin = True
  2784. # maximum number of concurrent connections
  2785. self.maxConnections = 0
  2786. # number of trusted web servers in front of this server
  2787. self.trustXForwardedFor = 0
  2788. def setProtocolOptions(self,
  2789. versions=None,
  2790. webStatus=None,
  2791. utf8validateIncoming=None,
  2792. maskServerFrames=None,
  2793. requireMaskedClientFrames=None,
  2794. applyMask=None,
  2795. maxFramePayloadSize=None,
  2796. maxMessagePayloadSize=None,
  2797. autoFragmentSize=None,
  2798. failByDrop=None,
  2799. echoCloseCodeReason=None,
  2800. openHandshakeTimeout=None,
  2801. closeHandshakeTimeout=None,
  2802. tcpNoDelay=None,
  2803. perMessageCompressionAccept=None,
  2804. autoPingInterval=None,
  2805. autoPingTimeout=None,
  2806. autoPingSize=None,
  2807. autoPingRestartOnAnyTraffic=None,
  2808. serveFlashSocketPolicy=None,
  2809. flashSocketPolicy=None,
  2810. allowedOrigins=None,
  2811. allowNullOrigin=False,
  2812. maxConnections=None,
  2813. trustXForwardedFor=None):
  2814. """
  2815. Implements :func:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory.setProtocolOptions`
  2816. """
  2817. if versions is not None:
  2818. for v in versions:
  2819. if v not in WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS:
  2820. raise Exception('invalid WebSocket protocol version {} (allowed values: {})'.format(v, WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS))
  2821. if set(versions) != set(self.versions):
  2822. self.versions = versions
  2823. if webStatus is not None and webStatus != self.webStatus:
  2824. self.webStatus = webStatus
  2825. if utf8validateIncoming is not None and utf8validateIncoming != self.utf8validateIncoming:
  2826. self.utf8validateIncoming = utf8validateIncoming
  2827. if requireMaskedClientFrames is not None and requireMaskedClientFrames != self.requireMaskedClientFrames:
  2828. self.requireMaskedClientFrames = requireMaskedClientFrames
  2829. if maskServerFrames is not None and maskServerFrames != self.maskServerFrames:
  2830. self.maskServerFrames = maskServerFrames
  2831. if applyMask is not None and applyMask != self.applyMask:
  2832. self.applyMask = applyMask
  2833. if maxFramePayloadSize is not None and maxFramePayloadSize != self.maxFramePayloadSize:
  2834. self.maxFramePayloadSize = maxFramePayloadSize
  2835. if maxMessagePayloadSize is not None and maxMessagePayloadSize != self.maxMessagePayloadSize:
  2836. self.maxMessagePayloadSize = maxMessagePayloadSize
  2837. if autoFragmentSize is not None and autoFragmentSize != self.autoFragmentSize:
  2838. self.autoFragmentSize = autoFragmentSize
  2839. if failByDrop is not None and failByDrop != self.failByDrop:
  2840. self.failByDrop = failByDrop
  2841. if echoCloseCodeReason is not None and echoCloseCodeReason != self.echoCloseCodeReason:
  2842. self.echoCloseCodeReason = echoCloseCodeReason
  2843. if openHandshakeTimeout is not None and openHandshakeTimeout != self.openHandshakeTimeout:
  2844. self.openHandshakeTimeout = openHandshakeTimeout
  2845. if closeHandshakeTimeout is not None and closeHandshakeTimeout != self.closeHandshakeTimeout:
  2846. self.closeHandshakeTimeout = closeHandshakeTimeout
  2847. if tcpNoDelay is not None and tcpNoDelay != self.tcpNoDelay:
  2848. self.tcpNoDelay = tcpNoDelay
  2849. if perMessageCompressionAccept is not None and perMessageCompressionAccept != self.perMessageCompressionAccept:
  2850. self.perMessageCompressionAccept = perMessageCompressionAccept
  2851. if autoPingInterval is not None and autoPingInterval != self.autoPingInterval:
  2852. self.autoPingInterval = autoPingInterval
  2853. if autoPingTimeout is not None and autoPingTimeout != self.autoPingTimeout:
  2854. self.autoPingTimeout = autoPingTimeout
  2855. if autoPingSize is not None and autoPingSize != self.autoPingSize:
  2856. assert(type(autoPingSize) == float or type(autoPingSize) == int)
  2857. assert(12 <= autoPingSize <= 125)
  2858. self.autoPingSize = autoPingSize
  2859. if autoPingRestartOnAnyTraffic is not None and autoPingRestartOnAnyTraffic != self.autoPingRestartOnAnyTraffic:
  2860. assert(type(autoPingRestartOnAnyTraffic) == bool)
  2861. self.autoPingRestartOnAnyTraffic = autoPingRestartOnAnyTraffic
  2862. if serveFlashSocketPolicy is not None and serveFlashSocketPolicy != self.serveFlashSocketPolicy:
  2863. self.serveFlashSocketPolicy = serveFlashSocketPolicy
  2864. if flashSocketPolicy is not None and flashSocketPolicy != self.flashSocketPolicy:
  2865. self.flashSocketPolicy = flashSocketPolicy
  2866. if allowedOrigins is not None and allowedOrigins != self.allowedOrigins:
  2867. self.allowedOrigins = allowedOrigins
  2868. self.allowedOriginsPatterns = wildcards2patterns(self.allowedOrigins)
  2869. if allowNullOrigin not in [True, False]:
  2870. raise ValueError('allowNullOrigin must be a bool')
  2871. self.allowNullOrigin = allowNullOrigin
  2872. if maxConnections is not None and maxConnections != self.maxConnections:
  2873. assert(type(maxConnections) == int)
  2874. assert(maxConnections >= 0)
  2875. self.maxConnections = maxConnections
  2876. if trustXForwardedFor is not None and trustXForwardedFor != self.trustXForwardedFor:
  2877. assert(type(trustXForwardedFor) == int)
  2878. assert(trustXForwardedFor >= 0)
  2879. self.trustXForwardedFor = trustXForwardedFor
  2880. def getConnectionCount(self):
  2881. """
  2882. Get number of currently connected clients.
  2883. :returns: Number of currently connected clients.
  2884. """
  2885. return self.countConnections
  2886. class WebSocketClientProtocol(WebSocketProtocol):
  2887. """
  2888. Protocol base class for WebSocket clients.
  2889. """
  2890. CONFIG_ATTRS = WebSocketProtocol.CONFIG_ATTRS_COMMON + WebSocketProtocol.CONFIG_ATTRS_CLIENT
  2891. def onConnecting(self, transport_details: TransportDetails) -> Optional[ConnectingRequest]:
  2892. """
  2893. Callback fired after the connection is established, but before the
  2894. handshake has started. This may return a
  2895. :class:`autobahn.websocket.types.ConnectingRequest` instance
  2896. (or a future which resolves to one) to control aspects of the
  2897. handshake (or None for defaults)
  2898. :param transport_details: Details of the transport underlying the WebSocket connection being established.
  2899. :returns: A :class:`autobahn.websocket.types.ConnectingRequest`
  2900. instance is returned to indicate which options should be
  2901. used for this connection. If you wish to use the default
  2902. behavior, ``None`` may be returned (this is the default).
  2903. """
  2904. def onConnect(self, response: ConnectionResponse) -> NoneType:
  2905. """
  2906. Callback fired directly after WebSocket opening handshake when new WebSocket
  2907. connection was established from the client to a server.
  2908. :param response: WebSocket connection response information sent by server.
  2909. """
  2910. def _connectionMade(self):
  2911. """
  2912. Called by network framework when new transport connection to server was established. Default
  2913. implementation will start the initial WebSocket opening handshake (or proxy connect).
  2914. When overriding in derived class, make sure to call this base class
  2915. implementation _before_ your code.
  2916. """
  2917. self.log.debug('{func}: connection accepted from peer {peer}',
  2918. func=hltype(self._connectionMade), peer=self.peer)
  2919. WebSocketProtocol._connectionMade(self)
  2920. if not self.factory.isServer and self.factory.proxy is not None:
  2921. # start by doing a HTTP/CONNECT for explicit proxies
  2922. self.startProxyConnect()
  2923. else:
  2924. # immediately start with the WebSocket opening handshake
  2925. self.startHandshake()
  2926. def _connectionLost(self, reason):
  2927. """
  2928. Called by network framework when established transport connection to server was lost. Default
  2929. implementation will tear down all state properly.
  2930. When overriding in derived class, make sure to call this base class
  2931. implementation _after_ your code.
  2932. """
  2933. self.log.debug('{func}: connection lost to peer {peer}: reason={reason}',
  2934. func=hltype(self._connectionLost), peer=self.peer, reason=hlval(reason))
  2935. WebSocketProtocol._connectionLost(self, reason)
  2936. def startProxyConnect(self):
  2937. """
  2938. Connect to explicit proxy.
  2939. """
  2940. # construct proxy connect HTTP request
  2941. #
  2942. request = 'CONNECT {}:{} HTTP/1.1\x0d\x0a'.format(self.factory.host, self.factory.port)
  2943. request += 'Host: {}:{}\x0d\x0a'.format(self.factory.host, self.factory.port)
  2944. request += '\x0d\x0a'
  2945. self.log.debug("{request}", request=request)
  2946. self.sendData(request.encode('utf8'))
  2947. def processProxyConnect(self):
  2948. """
  2949. Process HTTP/CONNECT response from server.
  2950. """
  2951. # only proceed when we have fully received the HTTP request line and all headers
  2952. #
  2953. end_of_header = self.data.find(b"\x0d\x0a\x0d\x0a")
  2954. if end_of_header >= 0:
  2955. http_response_data = self.data[:end_of_header + 4]
  2956. self.log.debug(
  2957. "received HTTP response:\n\n{response}\n\n",
  2958. response=http_response_data,
  2959. )
  2960. # extract HTTP status line and headers
  2961. #
  2962. (http_status_line, http_headers, http_headers_cnt) = parseHttpHeader(http_response_data)
  2963. # validate proxy connect response
  2964. #
  2965. self.log.debug(
  2966. "received HTTP status line for proxy connect request : {status}",
  2967. status=http_status_line,
  2968. )
  2969. self.log.debug(
  2970. "received HTTP headers for proxy connect request : {headers}",
  2971. headers=http_headers,
  2972. )
  2973. # Response Line
  2974. #
  2975. sl = http_status_line.split()
  2976. if len(sl) < 2:
  2977. return self.failProxyConnect('Bad HTTP response status line "{}"'.format(http_status_line))
  2978. # HTTP version
  2979. #
  2980. http_version = sl[0].strip()
  2981. if http_version not in ("HTTP/1.1", "HTTP/1.0"):
  2982. return self.failProxyConnect('Unsupported HTTP version "{}"'.format(http_version))
  2983. # HTTP status code
  2984. #
  2985. try:
  2986. status_code = int(sl[1].strip())
  2987. except ValueError:
  2988. return self.failProxyConnect('Bad HTTP status code "{}"'.format(sl[1].strip()))
  2989. if not (200 <= status_code < 300):
  2990. # FIXME: handle redirects
  2991. # FIXME: handle authentication required
  2992. if len(sl) > 2:
  2993. reason = ' '.join(sl[2:])
  2994. else:
  2995. reason = ''
  2996. return self.failProxyConnect('HTTP proxy connect failed ({}): "{}"'.format(status_code, reason))
  2997. # Ok, got complete response for HTTP/CONNECT, remember rest (if any)
  2998. #
  2999. self.data = self.data[end_of_header + 4:]
  3000. # opening handshake completed, move WebSocket connection into OPEN state
  3001. #
  3002. self.state = WebSocketProtocol.STATE_CONNECTING
  3003. # process rest of buffered data, if any
  3004. #
  3005. if len(self.data) > 0:
  3006. self.consumeData()
  3007. # now start WebSocket opening handshake
  3008. #
  3009. if self.factory.isSecure:
  3010. self.startTLS()
  3011. self.startHandshake()
  3012. def failProxyConnect(self, reason):
  3013. """
  3014. During initial explicit proxy connect, the server response indicates some failure and we drop the
  3015. connection.
  3016. """
  3017. self.log.debug("failing proxy connect ('{reason}')", reason=reason)
  3018. self.dropConnection(abort=True)
  3019. def startHandshake(self):
  3020. """
  3021. Start WebSocket opening handshake.
  3022. """
  3023. self.log.debug('{meth}: starting handshake with transport_details=\n{transport_details}',
  3024. meth=hltype(self.startHandshake),
  3025. transport_details=pformat(self._transport_details.marshal()))
  3026. # ask our specialized framework-specific (or user-code) for a
  3027. # ConnectingRequest instance
  3028. options_d = txaio.as_future(self.onConnecting, self._transport_details)
  3029. def got_options(request_options):
  3030. """
  3031. onConnecting succeeded and returned options
  3032. """
  3033. if request_options is None:
  3034. # Note, before onConnecting was added, everything came
  3035. # from self.factory so we get the required parameters from
  3036. # there still by default
  3037. request_options = ConnectingRequest(
  3038. # required (no defaults):
  3039. host=self.factory.host,
  3040. port=self.factory.port,
  3041. resource=self.factory.resource,
  3042. # optional (useful defaults):
  3043. headers=self.factory.headers, # might be None
  3044. useragent=self.factory.useragent, # might be None
  3045. origin=self.factory.origin, # might be None
  3046. protocols=self.factory.protocols, # might be None
  3047. )
  3048. self._actuallyStartHandshake(request_options)
  3049. return request_options
  3050. def options_failed(fail):
  3051. self.log.error(
  3052. "{meth} onConnecting failed: {fail}",
  3053. fail=fail,
  3054. meth=hltype(self.startHandshake),
  3055. )
  3056. self.dropConnection(abort=False)
  3057. # return fail
  3058. return None
  3059. txaio.add_callbacks(options_d, got_options, options_failed)
  3060. return options_d
  3061. def _actuallyStartHandshake(self, request_options):
  3062. """
  3063. Internal helper.
  3064. Actually send the WebSocket opening handshake after receiving
  3065. valid request options.
  3066. """
  3067. # construct WS opening handshake HTTP header
  3068. #
  3069. request = 'GET {} HTTP/1.1\x0d\x0a'.format(request_options.resource)
  3070. if request_options.useragent is not None and request_options.useragent != "":
  3071. request += 'User-Agent: {}\x0d\x0a'.format(request_options.useragent)
  3072. request += 'Host: {}:{}\x0d\x0a'.format(request_options.host, request_options.port)
  3073. request += 'Upgrade: WebSocket\x0d\x0a'
  3074. request += 'Connection: Upgrade\x0d\x0a'
  3075. # this seems to prohibit some non-compliant proxies from removing the
  3076. # connection "Upgrade" header
  3077. # See also:
  3078. # http://www.ietf.org/mail-archive/web/hybi/current/msg09841.html
  3079. # http://code.google.com/p/chromium/issues/detail?id=148908
  3080. #
  3081. request += 'Pragma: no-cache\x0d\x0a'
  3082. request += 'Cache-Control: no-cache\x0d\x0a'
  3083. # optional, user supplied additional HTTP headers
  3084. #
  3085. for uh in request_options.headers.items():
  3086. request += '{}: {}\x0d\x0a'.format(uh[0], uh[1])
  3087. # handshake random key
  3088. #
  3089. self.websocket_key = base64.b64encode(os.urandom(16))
  3090. request += 'Sec-WebSocket-Key: {}\x0d\x0a'.format(self.websocket_key.decode())
  3091. # optional origin announced
  3092. #
  3093. if request_options.origin:
  3094. if self.version > 10:
  3095. request += 'Origin: {}\x0d\x0a'.format(request_options.origin)
  3096. else:
  3097. request += 'Sec-WebSocket-Origin: {}\x0d\x0a'.format(request_options.origin)
  3098. # optional list of WS subprotocols announced
  3099. #
  3100. if len(request_options.protocols) > 0:
  3101. request += 'Sec-WebSocket-Protocol: {}\x0d\x0a'.format(','.join(request_options.protocols))
  3102. # extensions
  3103. #
  3104. extensions = []
  3105. # permessage-compress offers
  3106. #
  3107. for offer in self.perMessageCompressionOffers:
  3108. extensions.append(offer.get_extension_string())
  3109. if len(extensions) > 0:
  3110. request += 'Sec-WebSocket-Extensions: {}\x0d\x0a'.format(','.join(extensions))
  3111. # set WS protocol version
  3112. #
  3113. request += 'Sec-WebSocket-Version: {}\x0d\x0a'.format(WebSocketProtocol.SPEC_TO_PROTOCOL_VERSION[self.version])
  3114. request += '\x0d\x0a'
  3115. self.http_request_data = request.encode('utf8')
  3116. self.sendData(self.http_request_data)
  3117. self.log.debug('{meth}: sent HTTP request:\n{request}', meth=hltype(self._actuallyStartHandshake),
  3118. request=request)
  3119. def processHandshake(self):
  3120. """
  3121. Process WebSocket opening handshake response from server.
  3122. """
  3123. # only proceed when we have fully received the HTTP request line and all headers
  3124. #
  3125. end_of_header = self.data.find(b"\x0d\x0a\x0d\x0a")
  3126. if end_of_header >= 0:
  3127. self.http_response_data: bytes = self.data[:end_of_header + 4]
  3128. self.log.debug('{meth}: received HTTP response:\n{response}', meth=hltype(self.processHandshake),
  3129. response=self.http_response_data.decode('utf8'))
  3130. # extract HTTP status line and headers
  3131. #
  3132. (self.http_status_line, self.http_headers, http_headers_cnt) = parseHttpHeader(self.http_response_data)
  3133. # validate WebSocket opening handshake server response
  3134. #
  3135. self.log.debug(
  3136. "received HTTP status line in opening handshake : {status}",
  3137. status=self.http_status_line,
  3138. )
  3139. self.log.debug(
  3140. "received HTTP headers in opening handshake : {headers}",
  3141. headers=self.http_headers,
  3142. )
  3143. # Response Line
  3144. #
  3145. sl = self.http_status_line.split()
  3146. if len(sl) < 2:
  3147. return self.failHandshake('Bad HTTP response status line "{}"'.format(self.http_status_line))
  3148. # HTTP version
  3149. #
  3150. http_version = sl[0].strip()
  3151. if http_version != "HTTP/1.1":
  3152. return self.failHandshake('Unsupported HTTP version "{}"'.format(http_version))
  3153. # HTTP status code
  3154. #
  3155. try:
  3156. status_code = int(sl[1].strip())
  3157. except ValueError:
  3158. return self.failHandshake('Bad HTTP status code "{}"'.format(sl[1].strip()))
  3159. if status_code != 101: # Switching Protocols
  3160. # FIXME: handle redirects
  3161. # FIXME: handle authentication required
  3162. if len(sl) > 2:
  3163. reason = ' '.join(sl[2:])
  3164. else:
  3165. reason = ''
  3166. return self.failHandshake('WebSocket connection upgrade failed [{}]: {}'.format(status_code, reason))
  3167. # Upgrade
  3168. #
  3169. if 'upgrade' not in self.http_headers:
  3170. return self.failHandshake("HTTP Upgrade header missing")
  3171. if self.http_headers["upgrade"].strip().lower() != "websocket":
  3172. return self.failHandshake('HTTP Upgrade header different from "websocket" (case-insensitive): "{}"'.format(self.http_headers["upgrade"]))
  3173. # Connection
  3174. #
  3175. if 'connection' not in self.http_headers:
  3176. return self.failHandshake("HTTP Connection header missing")
  3177. connectionUpgrade = False
  3178. for c in self.http_headers["connection"].split(","):
  3179. if c.strip().lower() == "upgrade":
  3180. connectionUpgrade = True
  3181. break
  3182. if not connectionUpgrade:
  3183. return self.failHandshake('HTTP Connection header does not include "upgrade" value (case-insensitive): "{}"'.format(self.http_headers["connection"]))
  3184. # compute Sec-WebSocket-Accept
  3185. #
  3186. if 'sec-websocket-accept' not in self.http_headers:
  3187. return self.failHandshake("HTTP Sec-WebSocket-Accept header missing in opening handshake reply")
  3188. else:
  3189. if http_headers_cnt["sec-websocket-accept"] > 1:
  3190. return self.failHandshake("HTTP Sec-WebSocket-Accept header appears more than once in opening handshake reply")
  3191. sec_websocket_accept_got = self.http_headers["sec-websocket-accept"].strip()
  3192. sha1 = hashlib.sha1()
  3193. sha1.update(self.websocket_key + WebSocketProtocol._WS_MAGIC)
  3194. sec_websocket_accept = base64.b64encode(sha1.digest()).decode()
  3195. if sec_websocket_accept_got != sec_websocket_accept:
  3196. return self.failHandshake('HTTP Sec-WebSocket-Accept bogus value : expected {} / got {}'.format(sec_websocket_accept, sec_websocket_accept_got))
  3197. # Sec-WebSocket-Extensions
  3198. #
  3199. # extensions effectively in use for this connection
  3200. #
  3201. self.websocket_extensions_in_use = []
  3202. if 'sec-websocket-extensions' in self.http_headers:
  3203. if http_headers_cnt["sec-websocket-extensions"] > 1:
  3204. return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake reply")
  3205. else:
  3206. # extensions select by server
  3207. #
  3208. websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"])
  3209. # process extensions selected by server
  3210. #
  3211. for (extension, params) in websocket_extensions:
  3212. self.log.debug(
  3213. "parsed WebSocket extension '{extension}' with params '{params}'",
  3214. extension=extension,
  3215. params=params,
  3216. )
  3217. # process permessage-compress extension
  3218. #
  3219. if extension in PERMESSAGE_COMPRESSION_EXTENSION:
  3220. # check that server only responded with 1 configuration ("PMCE")
  3221. #
  3222. if self._perMessageCompress is not None:
  3223. return self.failHandshake("multiple occurrence of a permessage-compress extension")
  3224. PMCE = PERMESSAGE_COMPRESSION_EXTENSION[extension]
  3225. try:
  3226. pmceResponse = PMCE['Response'].parse(params)
  3227. except Exception as e:
  3228. return self.failHandshake(str(e))
  3229. accept = self.perMessageCompressionAccept(pmceResponse)
  3230. if accept is None:
  3231. return self.failHandshake("WebSocket permessage-compress extension response from server denied by client")
  3232. self._perMessageCompress = PMCE['PMCE'].create_from_response_accept(self.factory.isServer, accept)
  3233. self.websocket_extensions_in_use.append(self._perMessageCompress)
  3234. else:
  3235. return self.failHandshake('server wants to use extension "{}" we did not request, have not implemented or did not enable'.format(extension))
  3236. # handle "subprotocol in use" - if any
  3237. #
  3238. self.websocket_protocol_in_use = None
  3239. if 'sec-websocket-protocol' in self.http_headers:
  3240. if http_headers_cnt["sec-websocket-protocol"] > 1:
  3241. return self.failHandshake("HTTP Sec-WebSocket-Protocol header appears more than once in opening handshake reply")
  3242. sp = str(self.http_headers["sec-websocket-protocol"].strip())
  3243. if sp != "":
  3244. if sp not in self.factory.protocols:
  3245. return self.failHandshake('subprotocol selected by server "{}" not in subprotocol list requested by client "{}"'.format(sp, self.factory.protocols))
  3246. else:
  3247. # ok, subprotocol in use
  3248. #
  3249. self.websocket_protocol_in_use = sp
  3250. # Ok, got complete HS input, remember rest (if any)
  3251. #
  3252. self.data = self.data[end_of_header + 4:]
  3253. # opening handshake completed, move WebSocket connection into OPEN state
  3254. #
  3255. self.state = WebSocketProtocol.STATE_OPEN
  3256. # cancel any opening HS timer if present
  3257. #
  3258. if self.openHandshakeTimeoutCall is not None:
  3259. self.log.debug("openHandshakeTimeoutCall.cancel")
  3260. self.openHandshakeTimeoutCall.cancel()
  3261. self.openHandshakeTimeoutCall = None
  3262. # init state
  3263. #
  3264. self.inside_message = False
  3265. self.current_frame = None
  3266. self.websocket_version = self.version
  3267. # automatic ping/pong
  3268. #
  3269. if self.autoPingInterval:
  3270. self.autoPingPendingCall = self.factory._batched_timer.call_later(
  3271. self.autoPingInterval,
  3272. self._sendAutoPing,
  3273. )
  3274. # we handle this symmetrical to server-side .. that is, give the
  3275. # client a chance to bail out .. i.e. on no subprotocol selected
  3276. # by server
  3277. response = ConnectionResponse(self.peer,
  3278. self.http_headers,
  3279. self.websocket_version,
  3280. self.websocket_protocol_in_use,
  3281. self.websocket_extensions_in_use)
  3282. d = txaio.as_future(self._onConnect, response)
  3283. def on_connect_success(res):
  3284. self.log.debug('onConnect callback completed successfully with result={res}', res=res)
  3285. if self.trackedTimings:
  3286. self.trackedTimings.track('onOpen')
  3287. self._onOpen()
  3288. txaio.resolve(self.is_open, None)
  3289. if len(self.data) > 0:
  3290. self.consumeData()
  3291. def on_connect_failed(fail):
  3292. self.log.error(
  3293. 'onConnect failed with {fail}',
  3294. fail=fail,
  3295. )
  3296. self._fail_connection(1000, '{}'.format(fail))
  3297. txaio.add_callbacks(d, on_connect_success, on_connect_failed)
  3298. return d
  3299. def failHandshake(self, reason):
  3300. """
  3301. During opening handshake the server response is invalid and we drop the
  3302. connection.
  3303. """
  3304. self.wasNotCleanReason = reason
  3305. self.log.info(
  3306. "failing WebSocket opening handshake ('{reason}')",
  3307. reason=reason,
  3308. )
  3309. self.dropConnection(abort=True)
  3310. class WebSocketClientFactory(WebSocketFactory):
  3311. """
  3312. A protocol factory for WebSocket clients.
  3313. Implements :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
  3314. """
  3315. protocol = WebSocketClientProtocol
  3316. """
  3317. The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketClientProtocol`.
  3318. """
  3319. isServer = False
  3320. """
  3321. Flag indicating if this factory is client- or server-side.
  3322. """
  3323. def __init__(self,
  3324. url=None,
  3325. origin=None,
  3326. protocols=None,
  3327. useragent='AutobahnPython/{}'.format(__version__),
  3328. headers=None,
  3329. proxy=None):
  3330. """
  3331. Implements :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory.__init__`
  3332. """
  3333. self.logOctets = False
  3334. self.logFrames = False
  3335. self.trackTimings = False
  3336. # batch up and chunk timers ("call_later")
  3337. self._batched_timer = txaio.make_batched_timer(
  3338. bucket_seconds=0.200,
  3339. chunk_size=1000,
  3340. )
  3341. # seed RNG which is used for WS opening handshake key and WS frame masks generation
  3342. random.seed()
  3343. # default WS session parameters
  3344. #
  3345. self.setSessionParameters(url, origin, protocols, useragent, headers, proxy)
  3346. # default WebSocket protocol options
  3347. #
  3348. self.resetProtocolOptions()
  3349. def setSessionParameters(self,
  3350. url=None,
  3351. origin=None,
  3352. protocols=None,
  3353. useragent=None,
  3354. headers=None,
  3355. proxy=None):
  3356. """
  3357. Implements :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory.setSessionParameters`
  3358. """
  3359. # parse WebSocket URI into components
  3360. (isSecure, host, port, resource, path, params) = parse_url(url or "ws://localhost")
  3361. self.url = url
  3362. self.isSecure = isSecure
  3363. self.host = host
  3364. self.port = port
  3365. self.resource = resource
  3366. self.path = path
  3367. self.params = params
  3368. self.origin = origin
  3369. self.protocols = protocols or []
  3370. self.useragent = useragent
  3371. self.headers = headers or {}
  3372. self.proxy = proxy
  3373. def resetProtocolOptions(self):
  3374. """
  3375. Implements :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory.resetProtocolOptions`
  3376. """
  3377. self.version = WebSocketProtocol.DEFAULT_SPEC_VERSION
  3378. self.utf8validateIncoming = True
  3379. self.acceptMaskedServerFrames = False
  3380. self.maskClientFrames = True
  3381. self.applyMask = True
  3382. self.maxFramePayloadSize = 0
  3383. self.maxMessagePayloadSize = 0
  3384. self.autoFragmentSize = 0
  3385. self.failByDrop = True
  3386. self.echoCloseCodeReason = False
  3387. self.serverConnectionDropTimeout = 1
  3388. self.openHandshakeTimeout = 5
  3389. self.closeHandshakeTimeout = 1
  3390. self.tcpNoDelay = True
  3391. # permessage-XXX extensions
  3392. #
  3393. self.perMessageCompressionOffers = []
  3394. self.perMessageCompressionAccept = lambda _: None
  3395. # automatic ping/pong ("heartbeating")
  3396. #
  3397. self.autoPingInterval = 0
  3398. self.autoPingTimeout = 0
  3399. self.autoPingSize = 12
  3400. # see: https://github.com/crossbario/autobahn-python/issues/1327 and _cancelAutoPingTimeoutCall
  3401. self.autoPingRestartOnAnyTraffic = True
  3402. def setProtocolOptions(self,
  3403. version=None,
  3404. utf8validateIncoming=None,
  3405. acceptMaskedServerFrames=None,
  3406. maskClientFrames=None,
  3407. applyMask=None,
  3408. maxFramePayloadSize=None,
  3409. maxMessagePayloadSize=None,
  3410. autoFragmentSize=None,
  3411. failByDrop=None,
  3412. echoCloseCodeReason=None,
  3413. serverConnectionDropTimeout=None,
  3414. openHandshakeTimeout=None,
  3415. closeHandshakeTimeout=None,
  3416. tcpNoDelay=None,
  3417. perMessageCompressionOffers=None,
  3418. perMessageCompressionAccept=None,
  3419. autoPingInterval=None,
  3420. autoPingTimeout=None,
  3421. autoPingSize=None,
  3422. autoPingRestartOnAnyTraffic=None):
  3423. """
  3424. Implements :func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory.setProtocolOptions`
  3425. """
  3426. if version is not None:
  3427. if version not in WebSocketProtocol.SUPPORTED_SPEC_VERSIONS:
  3428. raise Exception('invalid WebSocket draft version {} (allowed values: {})'.format(version, WebSocketProtocol.SUPPORTED_SPEC_VERSIONS))
  3429. if version != self.version:
  3430. self.version = version
  3431. if utf8validateIncoming is not None and utf8validateIncoming != self.utf8validateIncoming:
  3432. self.utf8validateIncoming = utf8validateIncoming
  3433. if acceptMaskedServerFrames is not None and acceptMaskedServerFrames != self.acceptMaskedServerFrames:
  3434. self.acceptMaskedServerFrames = acceptMaskedServerFrames
  3435. if maskClientFrames is not None and maskClientFrames != self.maskClientFrames:
  3436. self.maskClientFrames = maskClientFrames
  3437. if applyMask is not None and applyMask != self.applyMask:
  3438. self.applyMask = applyMask
  3439. if maxFramePayloadSize is not None and maxFramePayloadSize != self.maxFramePayloadSize:
  3440. self.maxFramePayloadSize = maxFramePayloadSize
  3441. if maxMessagePayloadSize is not None and maxMessagePayloadSize != self.maxMessagePayloadSize:
  3442. self.maxMessagePayloadSize = maxMessagePayloadSize
  3443. if autoFragmentSize is not None and autoFragmentSize != self.autoFragmentSize:
  3444. self.autoFragmentSize = autoFragmentSize
  3445. if failByDrop is not None and failByDrop != self.failByDrop:
  3446. self.failByDrop = failByDrop
  3447. if echoCloseCodeReason is not None and echoCloseCodeReason != self.echoCloseCodeReason:
  3448. self.echoCloseCodeReason = echoCloseCodeReason
  3449. if serverConnectionDropTimeout is not None and serverConnectionDropTimeout != self.serverConnectionDropTimeout:
  3450. self.serverConnectionDropTimeout = serverConnectionDropTimeout
  3451. if openHandshakeTimeout is not None and openHandshakeTimeout != self.openHandshakeTimeout:
  3452. self.openHandshakeTimeout = openHandshakeTimeout
  3453. if closeHandshakeTimeout is not None and closeHandshakeTimeout != self.closeHandshakeTimeout:
  3454. self.closeHandshakeTimeout = closeHandshakeTimeout
  3455. if tcpNoDelay is not None and tcpNoDelay != self.tcpNoDelay:
  3456. self.tcpNoDelay = tcpNoDelay
  3457. if perMessageCompressionOffers is not None and pickle.dumps(perMessageCompressionOffers) != pickle.dumps(self.perMessageCompressionOffers):
  3458. if type(perMessageCompressionOffers) == list:
  3459. #
  3460. # FIXME: more rigorous verification of passed argument
  3461. #
  3462. self.perMessageCompressionOffers = copy.deepcopy(perMessageCompressionOffers)
  3463. else:
  3464. raise Exception('invalid type {} for perMessageCompressionOffers - expected list'.format(type(perMessageCompressionOffers)))
  3465. if perMessageCompressionAccept is not None and perMessageCompressionAccept != self.perMessageCompressionAccept:
  3466. self.perMessageCompressionAccept = perMessageCompressionAccept
  3467. if autoPingInterval is not None and autoPingInterval != self.autoPingInterval:
  3468. self.autoPingInterval = autoPingInterval
  3469. if autoPingTimeout is not None and autoPingTimeout != self.autoPingTimeout:
  3470. self.autoPingTimeout = autoPingTimeout
  3471. if autoPingSize is not None and autoPingSize != self.autoPingSize:
  3472. assert(type(autoPingSize) == float or type(autoPingSize) == int)
  3473. assert(12 <= autoPingSize <= 125)
  3474. self.autoPingSize = autoPingSize
  3475. if autoPingRestartOnAnyTraffic is not None and autoPingRestartOnAnyTraffic != self.autoPingRestartOnAnyTraffic:
  3476. assert(type(autoPingRestartOnAnyTraffic) == bool)
  3477. self.autoPingRestartOnAnyTraffic = autoPingRestartOnAnyTraffic