diff options
author | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-21 00:02:17 +0000 |
---|---|---|
committer | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-21 00:02:17 +0000 |
commit | fc475649da19bd9b1302c23e2ac31980bcd10b6d (patch) | |
tree | f99bc1233aa3896577351b7680b17f78f8a64a0e /tools | |
parent | 6f796d60883fd0b41b296fe8a7498b7acff8c8fd (diff) | |
download | chromium_src-fc475649da19bd9b1302c23e2ac31980bcd10b6d.zip chromium_src-fc475649da19bd9b1302c23e2ac31980bcd10b6d.tar.gz chromium_src-fc475649da19bd9b1302c23e2ac31980bcd10b6d.tar.bz2 |
Initial checkin for devtools-based automation
Review URL: https://chromiumcodereview.appspot.com/10825463
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@152439 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools')
15 files changed, 1955 insertions, 0 deletions
diff --git a/tools/devtools_auto/OWNERS b/tools/devtools_auto/OWNERS new file mode 100644 index 0000000..5f68319 --- /dev/null +++ b/tools/devtools_auto/OWNERS @@ -0,0 +1,5 @@ +# The set noparent is temporary until src/OWNERS isn't *. +set noparent +nduca@chromium.org +alokp@chromium.org +dtu@chromium.org diff --git a/tools/devtools_auto/README b/tools/devtools_auto/README new file mode 100644 index 0000000..0d1d5f2 --- /dev/null +++ b/tools/devtools_auto/README @@ -0,0 +1,4 @@ +devtools_auto provides automation of chrome instances on top of the chrome developer tools protocol. + +The protocol we use: +https://developers.google.com/chrome-developer-tools/docs/remote-debugging diff --git a/tools/devtools_auto/third_party/websocket-client/.gitignore b/tools/devtools_auto/third_party/websocket-client/.gitignore new file mode 100644 index 0000000..c7d73ad --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/.gitignore @@ -0,0 +1,8 @@ +*.pyc +*~ +*\# +.\#* + +build +dist +websocket_client.egg-info diff --git a/tools/devtools_auto/third_party/websocket-client/LICENSE b/tools/devtools_auto/third_party/websocket-client/LICENSE new file mode 100644 index 0000000..c255f4a --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/LICENSE @@ -0,0 +1,506 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/tools/devtools_auto/third_party/websocket-client/MANIFEST.in b/tools/devtools_auto/third_party/websocket-client/MANIFEST.in new file mode 100644 index 0000000..9d5d250 --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.rst diff --git a/tools/devtools_auto/third_party/websocket-client/README.chromium b/tools/devtools_auto/third_party/websocket-client/README.chromium new file mode 100644 index 0000000..0ef7731 --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/README.chromium @@ -0,0 +1,19 @@ +Name: Python websocket-client +Short Name: websocket-client +URL: https://github.com/liris/websocket-client +Version: 0 +Revision: 861f9cf354833fe3992315b60292865c5245c821 +Date: Tue Jul 10 19:57:00 2012 -0700 +License: LGPL-2.1 +License File: NOT_SHIPPED +Security Critical: no + +Description: + +websocket-client module is WebSocket client for python. This provide the low +level APIs for WebSocket. All APIs are the synchronous functions. + +Used by the python code in devtools-auto to communicate with a running Chrome instance. + +Local Modifications: +None. diff --git a/tools/devtools_auto/third_party/websocket-client/README.rst b/tools/devtools_auto/third_party/websocket-client/README.rst new file mode 100644 index 0000000..1b7faeb --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/README.rst @@ -0,0 +1,140 @@ +================= +websocket-client +================= + +websocket-client module is WebSocket client for python. This provide the low level APIs for WebSocket. All APIs are the synchronous functions. + +websocket-client supports only hybi-13. + +License +============ + + - LGPL + +Installation +============= + +This module is tested on only Python 2.7. + +Type "python setup.py install" or "pip install websocket-client" to install. + +This module does not depend on any other module. + +Example +============ + +Low Level API example:: + + from websocket import create_connection + ws = create_connection("ws://echo.websocket.org/") + print "Sending 'Hello, World'..." + ws.send("Hello, World") + print "Sent" + print "Reeiving..." + result = ws.recv() + print "Received '%s'" % result + ws.close() + + +JavaScript websocket-like API example:: + + import websocket + import thread + import time + + def on_message(ws, message): + print message + + def on_error(ws, error): + print error + + def on_close(ws): + print "### closed ###" + + def on_open(ws): + def run(*args): + for i in range(3): + time.sleep(1) + ws.send("Hello %d" % i) + time.sleep(1) + ws.close() + print "thread terminating..." + thread.start_new_thread(run, ()) + + + if __name__ == "__main__": + websocket.enableTrace(True) + ws = websocket.WebSocketApp("ws://echo.websocket.org/", + on_message = on_message, + on_error = on_error, + on_close = on_close) + ws.on_open = on_open + + ws.run_forever() + + +wsdump.py +============ + +wsdump.py is simple WebSocket test(debug) tool. + +sample for echo.websocket.org:: + + $ wsdump.py ws://echo.websocket.org/ + Press Ctrl+C to quit + > Hello, WebSocket + < Hello, WebSocket + > How are you? + < How are you? + +Usage +--------- + +usage:: + wsdump.py [-h] [-v [VERBOSE]] ws_url + +WebSocket Simple Dump Tool + +positional arguments: + ws_url websocket url. ex. ws://echo.websocket.org/ + +optional arguments: + -h, --help show this help message and exit + + -v VERBOSE, --verbose VERBOSE set verbose mode. If set to 1, show opcode. If set to 2, enable to trace websocket module + +example:: + + $ wsdump.py ws://echo.websocket.org/ + $ wsdump.py ws://echo.websocket.org/ -v + $ wsdump.py ws://echo.websocket.org/ -vv + +ChangeLog +============ + +- v0.7.0 + + - fixed problem to read long data.(ISSUE#12) + - fix buffer size boundary violation + +- v0.6.0 + + - Patches: UUID4, self.keep_running, mask_key (ISSUE#11) + - add wsdump.py tool + +- v0.5.2 + + - fix Echo App Demo Throw Error: 'NoneType' object has no attribute 'opcode (ISSUE#10) + +- v0.5.1 + + - delete invalid print statement. + +- v0.5.0 + + - support hybi-13 protocol. + +- v0.4.1 + + - fix incorrect custom header order(ISSUE#1) + diff --git a/tools/devtools_auto/third_party/websocket-client/bin/wsdump.py b/tools/devtools_auto/third_party/websocket-client/bin/wsdump.py new file mode 100755 index 0000000..02b40af --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/bin/wsdump.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +import argparse +import code +import sys +import threading +import websocket +try: + import readline +except: + pass + + +OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) +ENCODING = getattr(sys.stdin, "encoding", "").lower() + +class VAction(argparse.Action): + def __call__(self, parser, args, values, option_string=None): + if values==None: + values = "1" + try: + values = int(values) + except ValueError: + values = values.count("v")+1 + setattr(args, self.dest, values) + +def parse_args(): + parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool") + parser.add_argument("url", metavar="ws_url", + help="websocket url. ex. ws://echo.websocket.org/") + parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction, + dest="verbose", + help="set verbose mode. If set to 1, show opcode. " + "If set to 2, enable to trace websocket module") + + return parser.parse_args() + + +class InteractiveConsole(code.InteractiveConsole): + def write(self, data): + sys.stdout.write("\033[2K\033[E") + # sys.stdout.write("\n") + sys.stdout.write("\033[34m" + data + "\033[39m") + sys.stdout.write("\n> ") + sys.stdout.flush() + + + def raw_input(self, prompt): + line = raw_input(prompt) + if ENCODING and ENCODING != "utf-8" and not isinstance(line, unicode): + line = line.decode(ENCODING).encode("utf-8") + elif isinstance(line, unicode): + line = encode("utf-8") + + return line + + +def main(): + args = parse_args() + console = InteractiveConsole() + ws = websocket.create_connection(args.url) + if args.verbose > 1: + websocket.enableTrace(True) + print "Press Ctrl+C to quit" + + def recv(): + frame = ws.recv_frame() + if not frame: + raise websocket.WebSocketException("Not a valid frame %s" % frame) + elif frame.opcode in OPCODE_DATA: + return (frame.opcode, frame.data) + elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: + ws.send_close() + return (frame.opcode, None) + elif frame.opcode == websocket.ABNF.OPCODE_PING: + ws.pong("Hi!") + + return None, None + + + def recv_ws(): + while True: + opcode, data = recv() + msg = None + if not args.verbose and opcode in OPCODE_DATA: + msg = "< %s" % data + elif args.verbose: + msg = "< %s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data) + + if msg: + console.write(msg) + + thread = threading.Thread(target=recv_ws) + thread.daemon = True + thread.start() + + while True: + try: + message = console.raw_input("> ") + ws.send(message) + except KeyboardInterrupt: + return + except EOFError: + return + + +if __name__ == "__main__": + try: + main() + except Exception, e: + print e diff --git a/tools/devtools_auto/third_party/websocket-client/data/header01.txt b/tools/devtools_auto/third_party/websocket-client/data/header01.txt new file mode 100644 index 0000000..3142b43 --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/data/header01.txt @@ -0,0 +1,6 @@ +HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade: WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/tools/devtools_auto/third_party/websocket-client/data/header02.txt b/tools/devtools_auto/third_party/websocket-client/data/header02.txt new file mode 100644 index 0000000..a9dd2ce --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/data/header02.txt @@ -0,0 +1,6 @@ +HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/tools/devtools_auto/third_party/websocket-client/examples/echo_client.py b/tools/devtools_auto/third_party/websocket-client/examples/echo_client.py new file mode 100644 index 0000000..1eb5f1b --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/examples/echo_client.py @@ -0,0 +1,12 @@ +import websocket + +if __name__ == "__main__": + websocket.enableTrace(True) + ws = websocket.create_connection("ws://echo.websocket.org/") + print "Sending 'Hello, World'..." + ws.send("Hello, World") + print "Sent" + print "Receiving..." + result = ws.recv() + print "Received '%s'" % result + ws.close() diff --git a/tools/devtools_auto/third_party/websocket-client/examples/echoapp_client.py b/tools/devtools_auto/third_party/websocket-client/examples/echoapp_client.py new file mode 100644 index 0000000..438e4b3 --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/examples/echoapp_client.py @@ -0,0 +1,41 @@ +import websocket +import thread +import time +import sys + +def on_message(ws, message): + print message + +def on_error(ws, error): + print error + +def on_close(ws): + print "### closed ###" + +def on_open(ws): + def run(*args): + for i in range(3): + # send the message, then wait + # so thread doesnt exit and socket + # isnt closed + ws.send("Hello %d" % i) + time.sleep(1) + + time.sleep(1) + ws.close() + print "Thread terminating..." + + thread.start_new_thread(run, ()) + +if __name__ == "__main__": + websocket.enableTrace(True) + if len(sys.argv) < 2: + host = "ws://echo.websocket.org/" + else: + host = sys.argv[1] + ws = websocket.WebSocketApp(host, + on_message = on_message, + on_error = on_error, + on_close = on_close) + ws.on_open = on_open + ws.run_forever() diff --git a/tools/devtools_auto/third_party/websocket-client/setup.py b/tools/devtools_auto/third_party/websocket-client/setup.py new file mode 100644 index 0000000..e11400e --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/setup.py @@ -0,0 +1,28 @@ +from setuptools import setup + +VERSION = "0.7.0" + + +setup( + name="websocket-client", + version=VERSION, + description="WebSocket client for python. hybi13 is supported.", + long_description=open("README.rst").read(), + author="liris", + author_email="liris.pp@gmail.com", + license="LGPL", + url="https://github.com/liris/websocket-client", + classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Programming Language :: Python", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", + "Intended Audience :: Developers", + ], + py_modules=["websocket"], + scripts=["bin/wsdump.py"] +) diff --git a/tools/devtools_auto/third_party/websocket-client/test_websocket.py b/tools/devtools_auto/third_party/websocket-client/test_websocket.py new file mode 100644 index 0000000..3471f4e --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/test_websocket.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +# + +import base64 +import uuid +import unittest + +# websocket-client +import websocket as ws + +TRACABLE=False + +def create_mask_key(n): + return "abcd" + +class StringSockMock: + def __init__(self): + self.set_data("") + self.sent = [] + + def set_data(self, data): + self.data = data + self.pos = 0 + self.len = len(data) + + def recv(self, bufsize): + if self.len < self.pos: + return + buf = self.data[self.pos: self.pos + bufsize] + self.pos += bufsize + return buf + + def send(self, data): + self.sent.append(data) + + +class HeaderSockMock(StringSockMock): + def __init__(self, fname): + self.set_data(open(fname).read()) + self.sent = [] + + +class WebSocketTest(unittest.TestCase): + def setUp(self): + ws.enableTrace(TRACABLE) + + def tearDown(self): + pass + + def testDefaultTimeout(self): + self.assertEquals(ws.getdefaulttimeout(), None) + ws.setdefaulttimeout(10) + self.assertEquals(ws.getdefaulttimeout(), 10) + ws.setdefaulttimeout(None) + + def testParseUrl(self): + p = ws._parse_url("ws://www.example.com/r") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 80) + self.assertEquals(p[2], "/r") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com/r/") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 80) + self.assertEquals(p[2], "/r/") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com/") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 80) + self.assertEquals(p[2], "/") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 80) + self.assertEquals(p[2], "/") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com:8080/r") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 8080) + self.assertEquals(p[2], "/r") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com:8080/") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 8080) + self.assertEquals(p[2], "/") + self.assertEquals(p[3], False) + + p = ws._parse_url("ws://www.example.com:8080") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 8080) + self.assertEquals(p[2], "/") + self.assertEquals(p[3], False) + + p = ws._parse_url("wss://www.example.com:8080/r") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 8080) + self.assertEquals(p[2], "/r") + self.assertEquals(p[3], True) + + p = ws._parse_url("wss://www.example.com:8080/r?key=value") + self.assertEquals(p[0], "www.example.com") + self.assertEquals(p[1], 8080) + self.assertEquals(p[2], "/r?key=value") + self.assertEquals(p[3], True) + + self.assertRaises(ValueError, ws._parse_url, "http://www.example.com/r") + + def testWSKey(self): + key = ws._create_sec_websocket_key() + self.assert_(key != 24) + self.assert_("¥n" not in key) + + def testWsUtils(self): + sock = ws.WebSocket() + + key = "c6b8hTg4EeGb2gQMztV1/g==" + required_header = { + "upgrade": "websocket", + "connection": "upgrade", + "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=", + } + self.assertEquals(sock._validate_header(required_header, key), True) + + header = required_header.copy() + header["upgrade"] = "http" + self.assertEquals(sock._validate_header(header, key), False) + del header["upgrade"] + self.assertEquals(sock._validate_header(header, key), False) + + header = required_header.copy() + header["connection"] = "something" + self.assertEquals(sock._validate_header(header, key), False) + del header["connection"] + self.assertEquals(sock._validate_header(header, key), False) + + + header = required_header.copy() + header["sec-websocket-accept"] = "something" + self.assertEquals(sock._validate_header(header, key), False) + del header["sec-websocket-accept"] + self.assertEquals(sock._validate_header(header, key), False) + + def testReadHeader(self): + sock = ws.WebSocket() + sock.io_sock = sock.sock = HeaderSockMock("data/header01.txt") + status, header = sock._read_headers() + self.assertEquals(status, 101) + self.assertEquals(header["connection"], "upgrade") + + sock.io_sock = sock.sock = HeaderSockMock("data/header02.txt") + self.assertRaises(ws.WebSocketException, sock._read_headers) + + def testSend(self): + # TODO: add longer frame data + sock = ws.WebSocket() + sock.set_mask_key(create_mask_key) + s = sock.io_sock = sock.sock = HeaderSockMock("data/header01.txt") + sock.send("Hello") + self.assertEquals(s.sent[0], "\x81\x85abcd)\x07\x0f\x08\x0e") + + sock.send("こんにちは") + self.assertEquals(s.sent[1], "\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") + + sock.send(u"こんにちは") + self.assertEquals(s.sent[1], "\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") + + def testRecv(self): + # TODO: add longer frame data + sock = ws.WebSocket() + s = sock.io_sock = sock.sock = StringSockMock() + s.set_data("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") + data = sock.recv() + self.assertEquals(data, "こんにちは") + + s.set_data("\x81\x85abcd)\x07\x0f\x08\x0e") + data = sock.recv() + self.assertEquals(data, "Hello") + + def testWebSocket(self): + s = ws.create_connection("ws://echo.websocket.org/") #ws://localhost:8080/echo") + self.assertNotEquals(s, None) + s.send("Hello, World") + result = s.recv() + self.assertEquals(result, "Hello, World") + + s.send("こにゃにゃちは、世界") + result = s.recv() + self.assertEquals(result, "こにゃにゃちは、世界") + s.close() + + def testPingPong(self): + s = ws.create_connection("ws://echo.websocket.org/") + self.assertNotEquals(s, None) + s.ping("Hello") + s.pong("Hi") + s.close() + + def testSecureWebSocket(self): + s = ws.create_connection("wss://echo.websocket.org/") + self.assertNotEquals(s, None) + self.assert_(isinstance(s.io_sock, ws._SSLSocketWrapper)) + s.send("Hello, World") + result = s.recv() + self.assertEquals(result, "Hello, World") + s.send("こにゃにゃちは、世界") + result = s.recv() + self.assertEquals(result, "こにゃにゃちは、世界") + s.close() + + def testWebSocketWihtCustomHeader(self): + s = ws.create_connection("ws://echo.websocket.org/", + headers={"User-Agent": "PythonWebsocketClient"}) + self.assertNotEquals(s, None) + s.send("Hello, World") + result = s.recv() + self.assertEquals(result, "Hello, World") + s.close() + + def testAfterClose(self): + from socket import error + s = ws.create_connection("ws://echo.websocket.org/") + self.assertNotEquals(s, None) + s.close() + self.assertRaises(error, s.send, "Hello") + self.assertRaises(error, s.recv) + + def testUUID4(self): + """ WebSocket key should be a UUID4. + """ + key = ws._create_sec_websocket_key() + u = uuid.UUID(bytes=base64.b64decode(key)) + self.assertEquals(4, u.version) + +class WebSocketAppTest(unittest.TestCase): + + class NotSetYet(object): + """ A marker class for signalling that a value hasn't been set yet. + """ + + def setUp(self): + ws.enableTrace(TRACABLE) + + WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() + WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() + WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + + def tearDown(self): + + WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() + WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() + WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + + def testKeepRunning(self): + """ A WebSocketApp should keep running as long as its self.keep_running + is not False (in the boolean context). + """ + + def on_open(self, *args, **kwargs): + """ Set the keep_running flag for later inspection and immediately + close the connection. + """ + WebSocketAppTest.keep_running_open = self.keep_running + self.close() + + def on_close(self, *args, **kwargs): + """ Set the keep_running flag for the test to use. + """ + WebSocketAppTest.keep_running_close = self.keep_running + + app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close) + app.run_forever() + + self.assertFalse(isinstance(WebSocketAppTest.keep_running_open, + WebSocketAppTest.NotSetYet)) + + self.assertFalse(isinstance(WebSocketAppTest.keep_running_close, + WebSocketAppTest.NotSetYet)) + + self.assertEquals(True, WebSocketAppTest.keep_running_open) + self.assertEquals(False, WebSocketAppTest.keep_running_close) + + def testSockMaskKey(self): + """ A WebSocketApp should forward the received mask_key function down + to the actual socket. + """ + + def my_mask_key_func(): + pass + + def on_open(self, *args, **kwargs): + """ Set the value so the test can use it later on and immediately + close the connection. + """ + WebSocketAppTest.get_mask_key_id = id(self.get_mask_key) + self.close() + + app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func) + app.run_forever() + + # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. + self.assertEquals(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func)) + + +if __name__ == "__main__": + unittest.main() + diff --git a/tools/devtools_auto/third_party/websocket-client/websocket.py b/tools/devtools_auto/third_party/websocket-client/websocket.py new file mode 100644 index 0000000..480bfc0 --- /dev/null +++ b/tools/devtools_auto/third_party/websocket-client/websocket.py @@ -0,0 +1,756 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" + + +import socket +from urlparse import urlparse +import os +import struct +import uuid +import hashlib +import base64 +import logging + +""" +websocket python client. +========================= + +This version support only hybi-13. +Please see http://tools.ietf.org/html/rfc6455 for protocol. +""" + + +# websocket supported version. +VERSION = 13 + +# closing frame status codes. +STATUS_NORMAL = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA_TYPE = 1003 +STATUS_STATUS_NOT_AVAILABLE = 1005 +STATUS_ABNORMAL_CLOSED = 1006 +STATUS_INVALID_PAYLOAD = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_INVALID_EXTENSION = 1010 +STATUS_UNEXPECTED_CONDITION = 1011 +STATUS_TLS_HANDSHAKE_ERROR = 1015 + +logger = logging.getLogger() + +class WebSocketException(Exception): + """ + websocket exeception class. + """ + pass + +class WebSocketConnectionClosedException(WebSocketException): + """ + If remote host closed the connection or some network error happened, + this exception will be raised. + """ + pass + +default_timeout = None +traceEnabled = False + +def enableTrace(tracable): + """ + turn on/off the tracability. + + tracable: boolean value. if set True, tracability is enabled. + """ + global traceEnabled + traceEnabled = tracable + if tracable: + if not logger.handlers: + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.DEBUG) + +def setdefaulttimeout(timeout): + """ + Set the global timeout setting to connect. + + timeout: default socket timeout time. This value is second. + """ + global default_timeout + default_timeout = timeout + +def getdefaulttimeout(): + """ + Return the global timeout setting(second) to connect. + """ + return default_timeout + +def _parse_url(url): + """ + parse url and the result is tuple of + (hostname, port, resource path and the flag of secure mode) + + url: url string. + """ + if ":" not in url: + raise ValueError("url is invalid") + + scheme, url = url.split(":", 1) + + parsed = urlparse(url, scheme="http") + if parsed.hostname: + hostname = parsed.hostname + else: + raise ValueError("hostname is invalid") + port = 0 + if parsed.port: + port = parsed.port + + is_secure = False + if scheme == "ws": + if not port: + port = 80 + elif scheme == "wss": + is_secure = True + if not port: + port = 443 + else: + raise ValueError("scheme %s is invalid" % scheme) + + if parsed.path: + resource = parsed.path + else: + resource = "/" + + if parsed.query: + resource += "?" + parsed.query + + return (hostname, port, resource, is_secure) + +def create_connection(url, timeout=None, **options): + """ + connect to url and return websocket object. + + Connect to url and return the WebSocket object. + Passing optional timeout parameter will set the timeout on the socket. + If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. + You can customize using 'options'. + If you set "header" dict object, you can set your own custom header. + + >>> conn = create_connection("ws://echo.websocket.org/", + ... header={"User-Agent: MyProgram", + ... "x-custom: header"}) + + + timeout: socket timeout time. This value is integer. + if you set None for this value, it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, the custom HTTP headers are added. + """ + websock = WebSocket() + websock.settimeout(timeout != None and timeout or default_timeout) + websock.connect(url, **options) + return websock + +_MAX_INTEGER = (1 << 32) -1 +_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) +_MAX_CHAR_BYTE = (1<<8) -1 + +# ref. Websocket gets an update, and it breaks stuff. +# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html + +def _create_sec_websocket_key(): + uid = uuid.uuid4() + return base64.encodestring(uid.bytes).strip() + +_HEADERS_TO_CHECK = { + "upgrade": "websocket", + "connection": "upgrade", + } + +class _SSLSocketWrapper(object): + def __init__(self, sock): + self.ssl = socket.ssl(sock) + + def recv(self, bufsize): + return self.ssl.read(bufsize) + + def send(self, payload): + return self.ssl.write(payload) + +_BOOL_VALUES = (0, 1) +def _is_bool(*values): + for v in values: + if v not in _BOOL_VALUES: + return False + + return True + +class ABNF(object): + """ + ABNF frame class. + see http://tools.ietf.org/html/rfc5234 + and http://tools.ietf.org/html/rfc6455#section-5.2 + """ + + # operation code values. + OPCODE_TEXT = 0x1 + OPCODE_BINARY = 0x2 + OPCODE_CLOSE = 0x8 + OPCODE_PING = 0x9 + OPCODE_PONG = 0xa + + # available operation code value tuple + OPCODES = (OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, + OPCODE_PING, OPCODE_PONG) + + # opcode human readable string + OPCODE_MAP = { + OPCODE_TEXT: "text", + OPCODE_BINARY: "binary", + OPCODE_CLOSE: "close", + OPCODE_PING: "ping", + OPCODE_PONG: "pong" + } + + # data length threashold. + LENGTH_7 = 0x7d + LENGTH_16 = 1 << 16 + LENGTH_63 = 1 << 63 + + def __init__(self, fin = 0, rsv1 = 0, rsv2 = 0, rsv3 = 0, + opcode = OPCODE_TEXT, mask = 1, data = ""): + """ + Constructor for ABNF. + please check RFC for arguments. + """ + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.mask = mask + self.data = data + self.get_mask_key = os.urandom + + @staticmethod + def create_frame(data, opcode): + """ + create frame to send text, binary and other data. + + data: data to send. This is string value(byte array). + if opcode is OPCODE_TEXT and this value is uniocde, + data value is conveted into unicode string, automatically. + + opcode: operation code. please see OPCODE_XXX. + """ + if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): + data = data.encode("utf-8") + # mask must be set if send data from client + return ABNF(1, 0, 0, 0, opcode, 1, data) + + def format(self): + """ + format this object to string(byte array) to send data to server. + """ + if not _is_bool(self.fin, self.rsv1, self.rsv2, self.rsv3): + raise ValueError("not 0 or 1") + if self.opcode not in ABNF.OPCODES: + raise ValueError("Invalid OPCODE") + length = len(self.data) + if length >= ABNF.LENGTH_63: + raise ValueError("data is too long") + + frame_header = chr(self.fin << 7 + | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 + | self.opcode) + if length < ABNF.LENGTH_7: + frame_header += chr(self.mask << 7 | length) + elif length < ABNF.LENGTH_16: + frame_header += chr(self.mask << 7 | 0x7e) + frame_header += struct.pack("!H", length) + else: + frame_header += chr(self.mask << 7 | 0x7f) + frame_header += struct.pack("!Q", length) + + if not self.mask: + return frame_header + self.data + else: + mask_key = self.get_mask_key(4) + return frame_header + self._get_masked(mask_key) + + def _get_masked(self, mask_key): + s = ABNF.mask(mask_key, self.data) + return mask_key + "".join(s) + + @staticmethod + def mask(mask_key, data): + """ + mask or unmask data. Just do xor for each byte + + mask_key: 4 byte string(byte). + + data: data to mask/unmask. + """ + _m = map(ord, mask_key) + _d = map(ord, data) + for i in range(len(_d)): + _d[i] ^= _m[i % 4] + s = map(chr, _d) + return "".join(s) + +class WebSocket(object): + """ + Low level WebSocket interface. + This class is based on + The WebSocket protocol draft-hixie-thewebsocketprotocol-76 + http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + + We can connect to the websocket server and send/recieve data. + The following example is a echo client. + + >>> import websocket + >>> ws = websocket.WebSocket() + >>> ws.connect("ws://echo.websocket.org") + >>> ws.send("Hello, Server") + >>> ws.recv() + 'Hello, Server' + >>> ws.close() + + get_mask_key: a callable to produce new mask keys, see the set_mask_key + function's docstring for more details + """ + def __init__(self, get_mask_key = None): + """ + Initalize WebSocket object. + """ + self.connected = False + self.io_sock = self.sock = socket.socket() + self.get_mask_key = get_mask_key + + def set_mask_key(self, func): + """ + set function to create musk key. You can custumize mask key generator. + Mainly, this is for testing purpose. + + func: callable object. the fuct must 1 argument as integer. + The argument means length of mask key. + This func must be return string(byte array), + which length is argument specified. + """ + self.get_mask_key = func + + def settimeout(self, timeout): + """ + Set the timeout to the websocket. + + timeout: timeout time(second). + """ + self.sock.settimeout(timeout) + + def gettimeout(self): + """ + Get the websocket timeout(second). + """ + return self.sock.gettimeout() + + def connect(self, url, **options): + """ + Connect to url. url is websocket url scheme. ie. ws://host:port/resource + You can customize using 'options'. + If you set "header" dict object, you can set your own custom header. + + >>> ws = WebSocket() + >>> ws.connect("ws://echo.websocket.org/", + ... header={"User-Agent: MyProgram", + ... "x-custom: header"}) + + timeout: socket timeout time. This value is integer. + if you set None for this value, + it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, + the custom HTTP headers are added. + + """ + hostname, port, resource, is_secure = _parse_url(url) + # TODO: we need to support proxy + self.sock.connect((hostname, port)) + if is_secure: + self.io_sock = _SSLSocketWrapper(self.sock) + self._handshake(hostname, port, resource, **options) + + def _handshake(self, host, port, resource, **options): + sock = self.io_sock + headers = [] + headers.append("GET %s HTTP/1.1" % resource) + headers.append("Upgrade: websocket") + headers.append("Connection: Upgrade") + if port == 80: + hostport = host + else: + hostport = "%s:%d" % (host, port) + headers.append("Host: %s" % hostport) + headers.append("Origin: %s" % hostport) + + key = _create_sec_websocket_key() + headers.append("Sec-WebSocket-Key: %s" % key) + headers.append("Sec-WebSocket-Version: %s" % VERSION) + if "header" in options: + headers.extend(options["header"]) + + headers.append("") + headers.append("") + + header_str = "\r\n".join(headers) + sock.send(header_str) + if traceEnabled: + logger.debug( "--- request header ---") + logger.debug( header_str) + logger.debug("-----------------------") + + status, resp_headers = self._read_headers() + if status != 101: + self.close() + raise WebSocketException("Handshake Status %d" % status) + + success = self._validate_header(resp_headers, key) + if not success: + self.close() + raise WebSocketException("Invalid WebSocket Header") + + self.connected = True + + def _validate_header(self, headers, key): + for k, v in _HEADERS_TO_CHECK.iteritems(): + r = headers.get(k, None) + if not r: + return False + r = r.lower() + if v != r: + return False + + result = headers.get("sec-websocket-accept", None) + if not result: + return False + result = result.lower() + + value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower() + return hashed == result + + def _read_headers(self): + status = None + headers = {} + if traceEnabled: + logger.debug("--- response header ---") + + while True: + line = self._recv_line() + if line == "\r\n": + break + line = line.strip() + if traceEnabled: + logger.debug(line) + if not status: + status_info = line.split(" ", 2) + status = int(status_info[1]) + else: + kv = line.split(":", 1) + if len(kv) == 2: + key, value = kv + headers[key.lower()] = value.strip().lower() + else: + raise WebSocketException("Invalid header") + + if traceEnabled: + logger.debug("-----------------------") + + return status, headers + + def send(self, payload, opcode = ABNF.OPCODE_TEXT): + """ + Send the data as string. + + payload: Payload must be utf-8 string or unicoce, + if the opcode is OPCODE_TEXT. + Otherwise, it must be string(byte array) + + opcode: operation code to send. Please see OPCODE_XXX. + """ + frame = ABNF.create_frame(payload, opcode) + if self.get_mask_key: + frame.get_mask_key = self.get_mask_key + data = frame.format() + self.io_sock.send(data) + if traceEnabled: + logger.debug("send: " + repr(data)) + + def ping(self, payload = ""): + """ + send ping data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PING) + + def pong(self, payload): + """ + send pong data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PONG) + + def recv(self): + """ + Receive string data(byte array) from the server. + + return value: string(byte array) value. + """ + opcode, data = self.recv_data() + return data + + def recv_data(self): + """ + Recieve data with operation code. + + return value: tuple of operation code and string(byte array) value. + """ + while True: + frame = self.recv_frame() + if not frame: + # handle error: + # 'NoneType' object has no attribute 'opcode' + raise WebSocketException("Not a valid frame %s" % frame) + elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): + return (frame.opcode, frame.data) + elif frame.opcode == ABNF.OPCODE_CLOSE: + self.send_close() + return (frame.opcode, None) + elif frame.opcode == ABNF.OPCODE_PING: + self.pong("Hi!") + + + def recv_frame(self): + """ + recieve data as frame from server. + + return value: ABNF frame object. + """ + header_bytes = self._recv(2) + if not header_bytes: + return None + b1 = ord(header_bytes[0]) + fin = b1 >> 7 & 1 + rsv1 = b1 >> 6 & 1 + rsv2 = b1 >> 5 & 1 + rsv3 = b1 >> 4 & 1 + opcode = b1 & 0xf + b2 = ord(header_bytes[1]) + mask = b2 >> 7 & 1 + length = b2 & 0x7f + + length_data = "" + if length == 0x7e: + length_data = self._recv(2) + length = struct.unpack("!H", length_data)[0] + elif length == 0x7f: + length_data = self._recv(8) + length = struct.unpack("!Q", length_data)[0] + + mask_key = "" + if mask: + mask_key = self._recv(4) + data = self._recv_strict(length) + if traceEnabled: + recieved = header_bytes + length_data + mask_key + data + logger.debug("recv: " + repr(recieved)) + + if mask: + data = ABNF.mask(mask_key, data) + + frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, mask, data) + return frame + + def send_close(self, status = STATUS_NORMAL, reason = ""): + """ + send close data to the server. + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + + + + def close(self, status = STATUS_NORMAL, reason = ""): + """ + Close Websocket object + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + if self.connected: + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + + try: + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + timeout = self.sock.gettimeout() + self.sock.settimeout(3) + try: + frame = self.recv_frame() + if logger.isEnabledFor(logging.DEBUG): + logger.error("close status: " + repr(frame.data)) + except: + pass + self.sock.settimeout(timeout) + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + self._closeInternal() + + def _closeInternal(self): + self.connected = False + self.sock.close() + self.io_sock = self.sock + + def _recv(self, bufsize): + bytes = self.io_sock.recv(bufsize) + if bytes == 0: + raise WebSocketConnectionClosedException() + return bytes + + def _recv_strict(self, bufsize): + remaining = bufsize + bytes = "" + while remaining: + bytes += self._recv(remaining) + remaining = bufsize - len(bytes) + + return bytes + + def _recv_line(self): + line = [] + while True: + c = self._recv(1) + line.append(c) + if c == "\n": + break + return "".join(line) + +class WebSocketApp(object): + """ + Higher level of APIs are provided. + The interface is like JavaScript WebSocket object. + """ + def __init__(self, url, + on_open = None, on_message = None, on_error = None, + on_close = None, keep_running = True, get_mask_key = None): + """ + url: websocket url. + on_open: callable object which is called at opening websocket. + this function has one argument. The arugment is this class object. + on_message: callbale object which is called when recieved data. + on_message has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is utf-8 string which we get from the server. + on_error: callable object which is called when we get error. + on_error has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is exception object. + on_close: callable object which is called when closed the connection. + this function has one argument. The arugment is this class object. + keep_running: a boolean flag indicating whether the app's main loop should + keep running, defaults to True + get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's + docstring for more information + """ + self.url = url + self.on_open = on_open + self.on_message = on_message + self.on_error = on_error + self.on_close = on_close + self.keep_running = keep_running + self.get_mask_key = get_mask_key + self.sock = None + + def send(self, data): + """ + send message. data must be utf-8 string or unicode. + """ + if self.sock.send(data) == 0: + raise WebSocketConnectionClosedException() + + def close(self): + """ + close websocket connection. + """ + self.keep_running = False + self.sock.close() + + def run_forever(self): + """ + run event loop for WebSocket framework. + This loop is infinite loop and is alive during websocket is available. + """ + if self.sock: + raise WebSocketException("socket is already opened") + try: + self.sock = WebSocket(self.get_mask_key) + self.sock.connect(self.url) + self._run_with_no_err(self.on_open) + while self.keep_running: + data = self.sock.recv() + if data is None: + break + self._run_with_no_err(self.on_message, data) + except Exception, e: + self._run_with_no_err(self.on_error, e) + finally: + self.sock.close() + self._run_with_no_err(self.on_close) + self.sock = None + + def _run_with_no_err(self, callback, *args): + if callback: + try: + callback(self, *args) + except Exception, e: + if logger.isEnabledFor(logging.DEBUG): + logger.error(e) + + +if __name__ == "__main__": + enableTrace(True) + ws = create_connection("ws://echo.websocket.org/") + print "Sending 'Hello, World'..." + ws.send("Hello, World") + print "Sent" + print "Receiving..." + result = ws.recv() + print "Received '%s'" % result + ws.close() |