Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+179
View File
@@ -0,0 +1,179 @@
# Changelog
## 1.2.7 (May 12, 2018)
* Fix segfault on large numbers of connections [#843]
## 1.2.6 (April 30, 2018)
* *Fix segfault when an Exception is raised from unbind callback (for real this time!)*
* Fix race condition while initializing the machine [#756]
* Fix for newer compilers where bind() and std::bind() conflict [#830, #831]
* Be verbose about SSL connection errors [#807]
* Avoid explicitly calling class methods when in class scope
* Java: Add EM_PROTO_SSL/TLS definitions [#773, #791]
* Java: return zero when sending data to a closed connection [#475, #804]
* Pure Ruby: Connection::error? calls report_connection_error_status [#801]
## 1.2.5 (July 27, 2017)
* Java: Use long for larger values in oneshot timer intervals [#784, #794]
## 1.2.4 (July 27, 2017)
* Java: Add EM_PROTO_SSL/TLS definitions [#773, #791]
* Fix IPv6 UDP get_peername [#788]
* Allow for larger values in oneshot timer intervals [#784, #793]
* Update extconf.rb to allow MinGW builds with OpenSSL 1.1.0 [#785]
## 1.2.3 (February 22, 2017)
* Pure Ruby: Add get_sockname [#308, #772]
* Fix segfault when an Exception is raised from unbind callback [#765, #766]
* Allow destructors to throw when compiling in >= C++11 [#767]
## 1.2.2 (January 23, 2017)
* Java: Fix Fixnum deprecated warning in Ruby 2.4+ [#759]
* Fix uncaught C++ exception in file watcher and raise InvalidSignature [#512, #757]
* Fix connection count off-by-one for epoll and kqueue [#750]
* Fix uninitialized variable warning in EM::P::HttpClient [#749]
* Fix missing initial value for EventableDescriptor NextHeartbeat [#748]
* Fix hostname resolution on Solaris, Ilumos, SmartOS, et al [#745, #746]
* Improve reliability of tests, reduce public Internet accesses in tests [#656, #666, #749]
## 1.2.1 (November 15, 2016)
* Throw strerror(errno) when getsockname or getpeername fail [#683]
* Use a single concrete implementation of getpeername/getsockname, the rest pure virtuals [#683]
* Use gai_strerror to get the failure string from getaddrinfo [#744]
* Fix deregistering descriptor when using KQUEUE [#728]
* Fix to enable to work an example code in EM::Pool [#731]
* LineText2: Add regular expression delimiter support [#706]
* Pure Ruby: EM rescue ECONNREFUSED on initial TCP connect [#741]
* Pure Ruby: EM SSL (working start_tls) [#712]
* Pure Ruby: EM fixes [#707]
* Java: Use Acceptors to get peer and sock names if not present in Connections [#743]
## 1.2.0.1 (March 15, 2016)
* Fix crash when accepting IPv6 connections due to struct sockaddr_in [#698, #699]
## 1.2.0 (March 15, 2016)
* Integrate work from the EventMachine-LE 1.1.x versions [#570]
* Add start_tls options :ecdh_curve, :dhparam, :fail_if_no_peer_cert [#195, #275, #399, #665]
* Add start_tls option :ssl_version for choosing SSL/TLS versions and ciphers [#359, #348, #603, #654]
* Add start_tls option :sni_hostname to be passed to TLS params [#593]
* Add method EM::Channel#num_subscribers to get the number of subscribers to a channel [#640]
* Add support for proc-sources in EM::Iterator [#639]
* Factor out method cleanup_machine to cleanup code from EM.run [#650]
* Replace Exception class with StandardError [#637]
* Close socket on close_connection even after close_connection_after_writing [#694]
* Allow reusing of datagram socket/setting bind device [#662]
* Handle deferred exceptions in reactor thread [#486]
* Reimplement Queue to avoid shift/push performance problem [#311]
* Windows: Switch from gethostbyname to getaddrinfo, support IPv6 addresses [#303, #630]
* Windows: Use rake-compiler-dock to cross-compile gems [#627]
* Windows: Add AppVeyor configuration for Windows CI testing [#578]
* Windows: Bump rake-compiler to version 0.9.x [#542]
* Fix compilation on AIX (w/ XLC) [#693]
* Fix build on OpenBSD [#690]
* Fix OpenSSL compile issue on AIX 7.1 [#678]
* Fix EventMachine.fork_reactor keeps the threadpool of the original process [#425]
* Fix to prevent event machine from stopping when a raise is done in an unbind [#327]
## 1.0.9.1 (January 14, 2016)
* Fix EPROTO not defined on Windows [#676]
* Fix missing cast to struct sockaddr * [#671]
* Fix bug in OpenSSL path detection [#675]
## 1.0.9 (January 13, 2016)
* Try more ways to detect OpenSSL [#602, #643, #661, #663, #668, #669]
* Use WSAGetLastError in pipe.cpp same as ed.cpp [#659]
* Test compiler flags with the C++ compiler and add them to CXXFLAGS [#634, #651]
* Restore silent-fail on unsupported EM.epoll and EM.kqueue [#638, #649]
* getDescriptorByFileno deprecated in JRuby 1.7.x, removed in JRuby 9000 [#642, #648]
* Add -Wno-address always-true because on Windows rb_fd_select [#578]
* Remove the WITHOUT_SSL constant [#578]
* Fix SSL error when the server replies a TLS Alert to our ClientHello [#544, #653]
* Use WSAStringToAddress in lieu of inet_pton for IPv6 address detection on Windows [#595, #632]
* Fix nasty TCP/IPv6 bug [#595, #632]
* Use select_large_fdset on Solaris [#611, #625]
* Detect the Solaris Studio compiler [#611, #625]
* Throw a message with strerror included [#136, #621]
## 1.0.8 (August 6, 2015)
* fix kqueue assertion failed, postpone ArmKqueueWriter until all events are processed [#51, #176, #372, #401, #619]
* fix Rubinius GC, crank the machine from Ruby space when running Rubinius [#201, #202, #617]
* test to show that LineText2 preserves whitespace and newlines [#32, #622]
* bump up compiler warnings and resolve them [#616]
* fix Windows x64 use uintptr_t instead of unsigned long for binding pointers [#612, #615]
* fix linetext2 unroll tail recursion to avoid stack level too deep [#609]
* fix for compilation with SSL on windows [#601]
* open file descriptors and sockets with O_CLOEXEC where possible [#298, #488, #591]
* fix SmtpClient: send second EHLO after STARTTLS. [#589]
* fix nul-terminated strings in C, use StringValueCStr instead of StringValuePtr
## 1.0.7 (February 10, 2015)
* fix delay in kqueue/epoll reactor shutdown when timers exist [#587]
* fix memory leak introduced in v1.0.5 [#586]
* expose EM.set_simultaneous_accept_count [#420]
* fix busy loop when EM.run and EM.next_tick are invoked from exception handler [#452]
## 1.0.6 (February 3, 2015)
* add support for Rubinius Process::Status [#568]
* small bugfixes for SmtpServer [#449]
* update buftok.rb [#547]
* fix assertion on Write() [#525]
* work around mkmf.rb bug preventing gem installation [#574]
* add pause/resume support to jruby reactor [#556]
* fix pure ruby reactor to use 127.0.0.1 instead of localhost [#439]
* fix compilation under macruby [#243]
* add chunked encoding to http client [#111]
* fix errors on win32 when dealing with pipes [1ea45498] [#105]
## 1.0.5 (February 2, 2015)
* use monotonic clocks on Linux, OS X, Solaris, and Windows [#563]
* use the rb_fd_* API to get autosized fd_sets [#502]
* add basic tests that the DNS resolver isn't leaking timers [#571]
* update to test-unit 2.x and improve various unit tests [#551]
* remove EventMachine_t::Popen code marked by ifdef OBSOLETE [#551]
* ruby 2.0 may fail at Queue.pop, so rescue and complain to $stderr [#551]
* set file handle to INVALID_HANDLE_VALUE after closing the file [#565]
* use `defined?` instead of rescuing NameError for flow control [#535]
* fix closing files and sockets on Windows [#564]
* fix file uploads in Windows [#562]
* catch failure to fork [#539]
* use chunks for SSL write [#545]
## 1.0.4 (December 19, 2014)
* add starttls_options to smtp server [#552]
* fix closesocket on windows [#497]
* fix build on ruby 2.2 [#503]
* fix build error on ruby 1.9 [#508]
* fix timer leak during dns resolution [#489]
* add concurrency validation to EM::Iterator [#468]
* add get_file_descriptor to get fd for a signature [#467]
* add EM.attach_server and EM.attach_socket_server [#465, #466]
* calling pause from receive_data takes effect immediately [#464]
* reactor_running? returns false after fork [#455]
* fix infinite loop on double close [edc4d0e6, #441, #445]
* fix compilation issue on llvm [#433]
* fix socket error codes on win32 [ff811a81]
* fix EM.stop latency when timers exist [8b613d05, #426]
* fix infinite loop when system time changes [1427a2c80, #428]
* fix crash when callin attach/detach in the same tick [#427]
* fix compilation issue on solaris [#416]
## 1.0.3 (March 8, 2013)
* EM.system was broken in 1.0.2 release [#413]
## 1.0.2 (March 8, 2013)
* binary win32 gems now include fastfilereader shim [#222]
* fix long-standing connection timeout issues [27fdd5b, igrigorik/em-http-request#222]
* http and line protocol cleanups [#193, #151]
* reactor return value cleanup [#225]
* fix double require from gemspec [#284]
* fix smtp server reset behavior [#351]
* fix EM.system argument handling [#322]
* ruby 1.9 compat in smtp server and stomp protocols [#349, #315]
* fix pause from post_init [#380]
## 1.0.1 (February 27, 2013)
* use rb_wait_for_single_fd() on ruby 2.0 to fix rb_thread_select() deprecation [#363]
* fix epoll/kqueue mode in ruby 2.0 by removing calls to rb_enable_interrupt() [#248, #389]
* fix memory leak when verifying ssl cerificates [#403]
* fix initial connection delay [#393, #374]
* fix build on windows [#371]
+281
View File
@@ -0,0 +1,281 @@
.
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, 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 or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
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 give any other recipients of the Program a copy of this License
along with the Program.
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 Program or any portion
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
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 Program, 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 Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) 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; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, 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 executable. However, as a
special exception, the source code 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.
If distribution of executable or 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 counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program 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.
5. 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 Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program 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 to
this License.
7. 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 Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program 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 Program.
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.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program 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.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 Program
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 Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, 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
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
+60
View File
@@ -0,0 +1,60 @@
EventMachine is copyrighted free software owned by Francis Cianfrocca
(blackhedd ... gmail.com). The Owner of this software permits you to
redistribute and/or modify the software under either the terms of the GPL
version 2 (see the file GPL), or the conditions below ("Ruby License"):
1. You may make and give away verbatim copies of the source form of this
software without restriction, provided that you retain ALL of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a) place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b) use the modified software only within your corporation or
organization.
c) give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d) make other distribution arrangements with the Owner.
3. You may distribute the software in object code or binary form,
provided that you do at least ONE of the following:
a) distribute the binaries and library files of the software,
together with instructions (in a manual page or equivalent)
on where to get the original distribution.
b) accompany the distribution with the machine-readable source of
the software.
c) give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d) make other distribution arrangements with the Owner.
4. You may modify and include parts of the software into any other
software (possibly commercial), provided you comply with the terms in
Sections 1, 2, and 3 above. But some files in the distribution
are not written by the Owner, so they may be made available to you
under different terms.
For the list of those files and their copying conditions, see the
file LEGAL.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whoever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
+110
View File
@@ -0,0 +1,110 @@
# About EventMachine [![Build Status](https://travis-ci.org/eventmachine/eventmachine.svg?branch=master)](https://travis-ci.org/eventmachine/eventmachine) [![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/e9b0603462905d5b9118/maintainability)](https://codeclimate.com/github/eventmachine/eventmachine/maintainability)
## What is EventMachine ##
EventMachine is an event-driven I/O and lightweight concurrency library for Ruby.
It provides event-driven I/O using the [Reactor pattern](http://en.wikipedia.org/wiki/Reactor_pattern),
much like [JBoss Netty](http://www.jboss.org/netty), [Apache MINA](http://mina.apache.org/),
Python's [Twisted](http://twistedmatrix.com), [Node.js](http://nodejs.org), libevent and libev.
EventMachine is designed to simultaneously meet two key needs:
* Extremely high scalability, performance and stability for the most demanding production environments.
* An API that eliminates the complexities of high-performance threaded network programming,
allowing engineers to concentrate on their application logic.
This unique combination makes EventMachine a premier choice for designers of critical networked
applications, including Web servers and proxies, email and IM production systems, authentication/authorization
processors, and many more.
EventMachine has been around since the early 2000s and is a mature and battle-tested library.
## What EventMachine is good for? ##
* Scalable event-driven servers. Examples: [Thin](http://code.macournoyer.com/thin/) or [Goliath](https://github.com/postrank-labs/goliath/).
* Scalable asynchronous clients for various protocols, RESTful APIs and so on. Examples: [em-http-request](https://github.com/igrigorik/em-http-request) or [amqp gem](https://github.com/ruby-amqp/amqp).
* Efficient network proxies with custom logic. Examples: [Proxymachine](https://github.com/mojombo/proxymachine/).
* File and network monitoring tools. Examples: [eventmachine-tail](https://github.com/jordansissel/eventmachine-tail) and [logstash](https://github.com/logstash/logstash).
## What platforms are supported by EventMachine? ##
EventMachine supports Ruby 1.8.7 through 2.6, REE, JRuby and **works well on Windows** as well
as many operating systems from the Unix family (Linux, Mac OS X, BSD flavors).
## Install the gem ##
Install it with [RubyGems](https://rubygems.org/)
gem install eventmachine
or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
gem "eventmachine"
## Getting started ##
For an introduction to EventMachine, check out:
* [blog post about EventMachine by Ilya Grigorik](http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/).
* [EventMachine Introductions by Dan Sinclair](http://everburning.com/news/eventmachine-introductions.html).
### Server example: Echo server ###
Here's a fully-functional echo server written with EventMachine:
```ruby
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
# Note that this will block current thread.
EventMachine.run {
EventMachine.start_server "127.0.0.1", 8081, EchoServer
}
```
## EventMachine documentation ##
Currently we only have [reference documentation](http://rdoc.info/github/eventmachine/eventmachine/frames) and a [wiki](https://github.com/eventmachine/eventmachine/wiki).
## Community and where to get help ##
* Join the [mailing list](http://groups.google.com/group/eventmachine) (Google Group)
* Join IRC channel #eventmachine on irc.freenode.net
## License and copyright ##
EventMachine is copyrighted free software made available under the terms
of either the GPL or Ruby's License.
Copyright: (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
## Alternatives ##
If you are unhappy with EventMachine and want to use Ruby, check out [Celluloid](https://celluloid.io/).
@@ -0,0 +1,27 @@
# EventMachine documentation guides #
Welcome to the documentation guides for [EventMachine](http://github.com/eventmachine/eventmachine),
a fast and simple event-processing library for Ruby programs (à la JBoss Netty, Twisted, Node.js
and so on).
## Guide list ##
* {file:docs/GettingStarted.md Getting started with EventMachine}
* {file:docs/EventDrivenServers.md Writing event-driven servers}
* {file:docs/EventDrivenClients.md Writing event-driven clients}
* {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery}
* {file:docs/TLS.md TLS (aka SSL)}
* {file:docs/Ecosystem.md EventMachine ecosystem}: Thin, Goliath, em-http-request, em-websockets, Proxymachine and beyond
* {file:docs/BlockingEventLoop.md On blocking the event loop: why it is harmful for performance and how to avoid it}
* {file:docs/LightweightConcurrency.md Lightweight concurrency with EventMachine}
* {file:docs/Deferrables.md Deferrables}
* {file:docs/ModernKernelInputOutputAPIs.md Brief introduction to epoll, kqueue, select}
* {file:docs/WorkingWithOtherIOSources.md Working with other IO sources such as the keyboard}
## Tell us what you think! ##
Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3)
or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered?
Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is
key to making documentation better.
@@ -0,0 +1,521 @@
# @title Getting Started with Ruby EventMachine
# @markup markdown
# @author Michael S. Klishin, Dan Sinclair
# Getting started with Ruby EventMachine #
## About this guide ##
This guide is a quick tutorial that helps you to get started with EventMachine for writing event-driven
servers, clients and using it as a lightweight concurrency library.
It should take about 20 minutes to read and study the provided code examples. This guide covers
* Installing EventMachine via [Rubygems](http://rubygems.org) and [Bundler](http://gembundler.com).
* Building an Echo server, the "Hello, world"-like code example of network servers.
* Building a simple chat, both server and client.
* Building a very small asynchronous Websockets client.
## Covered versions ##
This guide covers EventMachine v0.12.10 and 1.0 (including betas).
## Level ##
This guide assumes you are comfortable (but not necessary a guru) with the command line. On Microsoft Windows™,
we recommend you to use [JRuby](http://jruby.org) when running these examples.
## Installing EventMachine ##
### Make sure you have Ruby installed ###
This guide assumes you have one of the supported Ruby implementations installed:
* Ruby 1.8.7
* Ruby 1.9.2
* [JRuby](http://jruby.org) (we recommend 1.6)
* [Rubinius](http://rubini.us) 1.2 or higher
* [Ruby Enterprise Edition](http://www.rubyenterpriseedition.com)
EventMachine works on Microsoft Windows™.
### With Rubygems ###
To install the EventMachine gem do
gem install eventmachine
### With Bundler ###
gem "eventmachine"
### Verifying your installation ###
Lets verify your installation with this quick IRB session:
irb -rubygems
ruby-1.9.2-p180 :001 > require "eventmachine"
=> true
ruby-1.9.2-p180 :002 > EventMachine::VERSION
=> "1.0.0.beta.3"
## An Echo Server Example ##
Lets begin with the classic "Hello, world"-like example, an echo server. The echo server responds clients with the
same data that was provided. First, here's the code:
{include:file:examples/guides/getting\_started/01\_eventmachine\_echo_server.rb}
When run, the server binds to port 10000. We can connect using Telnet and verify it's working:
telnet localhost 10000
On my machine the output looks like:
~ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Let's send something to our server. Type in "Hello, EventMachine" and hit Enter. The server will respond with
the same string:
~ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello, EventMachine
# (here we hit Enter)
Hello, EventMachine
# (this ^^^ is our echo server reply)
It works! Congratulations, you now can tell your Node.js-loving friends that you "have done some event-driven programming, too".
Oh, and to stop Telnet, hit Control + Shift + ] and then Control + C.
Lets walk this example line by line and see what's going on. These lines
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
probably look familiar: you use [RubyGems](http://rubygems.org) (or [Bundler](http://gembundler.com/)) for dependencies and then require EventMachine gem. Boring.
Next:
class EchoServer < EventMachine::Connection
def receive_data(data)
send_data(data)
end
end
Is the implementation of our echo server. We define a class that inherits from {EventMachine::Connection}
and a handler (aka callback) for one event: when we receive data from a client.
EventMachine handles the connection setup, receiving data and passing it to our handler, {EventMachine::Connection#receive_data}.
Then we implement our protocol logic, which in the case of Echo is pretty trivial: we send back whatever we receive.
To do so, we're using {EventMachine::Connection#send_data}.
Lets modify the example to recognize `exit` command:
{include:file:examples/guides/getting\_started/02\_eventmachine\_echo_server\_that\_recognizes\_exit\_command.rb}
Our `receive\_data` changed slightly and now looks like this:
def receive_data(data)
if data.strip =~ /exit$/i
EventMachine.stop_event_loop
else
send_data(data)
end
end
Because incoming data has trailing newline character, we strip it off before matching it against a simple regular
expression. If the data ends in `exit`, we stop EventMachine event loop with {EventMachine.stop_event_loop}. This unblocks
main thread and it finishes execution, and our little program exits as the result.
To summarize this first example:
* Subclass {EventMachine::Connection} and override {EventMachine::Connection#send_data} to handle incoming data.
* Use {EventMachine.run} to start EventMachine event loop and then bind echo server with {EventMachine.start_server}.
* To stop the event loop, use {EventMachine.stop_event_loop} (aliased as {EventMachine.stop})
Lets move on to a slightly more sophisticated example that will introduce several more features and methods
EventMachine has to offer.
## A Simple Chat Server Example ##
Next we will write a simple chat. Initially clients will still use telnet to connect, but then we will add little
client application that will serve as a proxy between telnet and the chat server. This example is certainly longer
(~ 150 lines with whitespace and comments) so instead of looking at the final version and going through it line by line,
we will instead begin with a very simple version that only keeps track of connected clients and then add features
as we go.
To set some expectations about our example:
* It will keep track of connected clients
* It will support a couple of commands, à la IRC
* It will support direct messages using Twitter-like @usernames
* It won't use MongoDB, fibers or distributed map/reduce for anything but will be totally [Web Scale™](http://bit.ly/webscaletm) nonetheless. Maybe even [ROFLscale](http://bit.ly/roflscalevideo).
### Step one: detecting connections and disconnectons ###
First step looks like this:
{include:file:examples/guides/getting\_started/04\_simple\_chat\_server\_step\_one.rb}
We see familiar {EventMachine.run} and {EventMachine.start_server}, but also {EventMachine::Connection#post_init} and {EventMachine::Connection#unbind} we haven't
met yet. We don't use them in this code, so when are they run? Like {EventMachine::Connection#receive_data}, these methods are callbacks. EventMachine calls them
when certain events happen:
* {EventMachine#post_init} is called by the event loop immediately after the network connection has been established.
In the chat server example case, this is when a new client connects.
* {EventMachine#unbind} is called when client disconnects, connection is closed or is lost (because of a network issue, for example).
All our chat server does so far is logging connections or disconnections. What we want it to do next is to keep track of connected clients.
### Step two: keep track of connected clients ###
Next iteration of the code looks like this:
{include:file:examples/guides/getting\_started/05\_simple\_chat\_server\_step\_two.rb}
While the code we added is very straightforward, we have to clarify one this first: subclasses of {EventMachine::Connection} are instantiated by
EventMachine for every new connected peer. So for 10 connected chat clients, there will be 10 separate `SimpleChatServer` instances in our
server process. Like any other objects, they can be stored in a collection, can provide public API other objects use, can instantiate or inject
dependencies and in general live a happy life all Ruby objects live until garbage collection happens.
In the example above we use a @@class_variable to keep track of connected clients. In Ruby, @@class variables are accessible from instance
methods so we can add new connections to the list from `SimpleChatServer#post_init` and remove them in `SimpleChatServer#unbind`. We can also
filter connections by some criteria, as `SimpleChatServer#other_peers demonstrates`.
So, we keep track of connections but how do we identify them? For a chat app, it's pretty common to use usernames for that. Lets ask our clients
to enter usernames when they connect.
### Step three: adding usernames ##
To add usernames, we need to add a few things:
* We need to invite newly connected clients to enter their username.
* A reader (getter) method on our {EventMachine::Connection} subclass.
* An idea of connection state (keeping track of whether a particular participant had entered username before).
Here is one way to do it:
{include:file:examples/guides/getting\_started/06\_simple\_chat\_server\_step\_three.rb}
This is quite an update so lets take a look at each method individually. First, `SimpleChatServer#post_init`:
def post_init
@username = nil
puts "A client has connected..."
ask_username
end
To keep track of username we ask chat participants for, we add @username instance variable to our connection class. Connection
instances are just Ruby objects associated with a particular connected peer, so using @ivars is very natural. To make username
value accessible to other objects, we added a reader method that was not shown on the snippet above.
Lets dig into `SimpleChatServer#ask_username`:
def ask_username
self.send_line("[info] Enter your username:")
end # ask_username
# ...
def send_line(line)
self.send_data("#{line}\n")
end # send_line(line)
Nothing new here, we are using {EventMachine::Connection#send_data} which we have seen before.
In `SimpleChatServer#receive_data` we now have to check if the username was entered or we need
to ask for it:
def receive_data(data)
if entered_username?
handle_chat_message(data.strip)
else
handle_username(data.strip)
end
end
# ...
def entered_username?
!@username.nil? && !@username.empty?
end # entered_username?
Finally, handler of chat messages is not yet implemented:
def handle_chat_message(msg)
raise NotImplementedError
end
Lets try this example out using Telnet:
~ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
[info] Enter your username:
antares_
[info] Ohai, antares_
and the server output:
A client has connected...
antares_ has joined
This version requires you to remember how to terminate your Telnet session (Ctrl + Shift + ], then Ctrl + C).
It is annoying, so why don't we add the same `exit` command to our chat server?
### Step four: adding exit command and delivering chat messages ####
{include:file:examples/guides/getting\_started/07\_simple\_chat\_server\_step\_four.rb}
TBD
Lets test-drive this version. Client A:
~ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
[info] Enter your username:
michael
[info] Ohai, michael
Hi everyone
michael: Hi everyone
joe has joined the room
# here ^^^ client B connects, lets greet him
hi joe
michael: hi joe
joe: hey michael
# ^^^ client B replies
exit
# ^^^ out command in action
Connection closed by foreign host.
Client B:
~ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
[info] Enter your username:
joe
[info] Ohai, joe
michael: hi joe
# ^^^ client A greets us, lets reply
hey michael
joe: hey michael
exit
# ^^^ out command in action
Connection closed by foreign host.
And finally, the server output:
A client has connected...
michael has joined
A client has connected...
_antares has joined
[info] _antares has left
[info] michael has left
Our little char server now supports usernames, sending messages and the `exit` command. Next up, private (aka direct) messages.
### Step five: adding direct messages and one more command ###
To add direct messages, we come up with a simple convention: private messages begin with @username and may have optional colon before
message text, like this:
@joe: hey, how do you like eventmachine?
This convention makes parsing of messages simple so that we can concentrate on delivering them to a particular client connection.
Remember when we added `username` reader on our connection class? That tiny change makes this step possible: when a new direct
message comes in, we extract username and message text and then find then connection for @username in question:
#
# Message handling
#
def handle_chat_message(msg)
if command?(msg)
self.handle_command(msg)
else
if direct_message?(msg)
self.handle_direct_message(msg)
else
self.announce(msg, "#{@username}:")
end
end
end # handle_chat_message(msg)
def direct_message?(input)
input =~ DM_REGEXP
end # direct_message?(input)
def handle_direct_message(input)
username, message = parse_direct_message(input)
if connection = @@connected_clients.find { |c| c.username == username }
puts "[dm] @#{@username} => @#{username}"
connection.send_line("[dm] @#{@username}: #{message}")
else
send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
end
end # handle_direct_message(input)
def parse_direct_message(input)
return [$1, $2] if input =~ DM_REGEXP
end # parse_direct_message(input)
This snippet demonstrates how one connection instance can obtain another connection instance and send data to it.
This is a very powerful feature, consider just a few use cases:
* Peer-to-peer protocols
* Content-aware routing
* Efficient streaming with optional filtering
Less common use cases include extending C++ core of EventMachine to provide access to hardware that streams events that
can be re-broadcasted to any interested parties connected via TCP, UDP or something like AMQP or WebSockets. With this,
sky is the limit. Actually, EventMachine has several features for efficient proxying data between connections.
We will not cover them in this guide.
One last feature that we are going to add to our chat server is the `status` command that tells you current server time and how many people
are there in the chat room:
#
# Commands handling
#
def command?(input)
input =~ /(exit|status)$/i
end # command?(input)
def handle_command(cmd)
case cmd
when /exit$/i then self.close_connection
when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
end
end # handle_command(cmd)
Hopefully this piece of code is easy to follow. Try adding a few more commands, for example, the `whoishere` command that lists people
currently in the chat room.
In the end, our chat server looks like this:
{include:file:examples/guides/getting\_started/08\_simple\_chat\_server\_step\_five.rb}
We are almost done with the server but there are some closing thoughts.
### Step six: final version ###
Just in case, here is the final version of the chat server code we have built:
{include:file:examples/guides/getting\_started/03\_simple\_chat\_server.rb}
### Step seven: future directions and some closing thoughts ###
The chat server is just about 150 lines of Ruby including empty lines and comments, yet it has a few features most of chat server
examples never add. We did not, however, implement many other features that popular IRC clients like [Colloquy](http://colloquy.info) have:
* Chat moderation
* Multiple rooms
* Connection timeout detection
How would one go about implementing them? We thought it is worth discussing what else EventMachine has to offer and what ecosystem projects
one can use to build a really feature-rich Web-based IRC chat client.
With multiple rooms it's more or less straightforward, just add one more hash and a bunch of commands and use the information about which rooms participant
is in when you are delivering messages. There is nothing in EventMachine itself that can make the job much easier for developer.
To implement chat moderation feature you may want to do a few things:
* Work with client IP addresses. Maybe we want to consider everyone who connects from certain IPs a moderator.
* Access persistent data about usernames of moderators and their credentials.
Does EventMachine have anything to offer here? It does. To obtain peer IP address, take a look at {EventMachine::Connection#get_peername}. The name of this method is
a little bit misleading and originates from low-level socket programming APIs.
#### A whirlwind tour of the EventMachine ecosystem ####
To work with data stores you can use several database drivers that ship with EventMachine itself, however, quite often there are some 3rd party projects in
the EventMachine ecosystem that have more features, are faster or just better maintained. So we figured it will be helpful to provide a few pointers
to some of those projects:
* For MySQL, check out [em-mysql](https://github.com/eventmachine/em-mysql) project.
* For PostgreSQL, have a look at Mike Perham's [EventMachine-based PostgreSQL driver](https://github.com/mperham/em_postgresql).
* For Redis, there is a young but already popular [em-hiredis](https://github.com/mloughran/em-hiredis) library that combines EventMachine's non-blocking I/O with
extreme performance of the official Redis C client, [hiredis](https://github.com/antirez/hiredis).
* For MongoDB, see [em-mongo](https://github.com/bcg/em-mongo)
* For Cassandra, Mike Perham [added transport agnosticism feature](http://www.mikeperham.com/2010/02/09/cassandra-and-eventmachine/) to the [cassandra gem](https://rubygems.org/gems/cassandra).
[Riak](http://www.basho.com/products_riak_overview.php) and CouchDB talk HTTP so it's possible to use [em-http-request](https://github.com/igrigorik/em-http-request).
If you are aware of EventMachine-based non-blocking drivers for these databases, as well as for HBase, let us know on the [EventMachine mailing list](http://groups.google.com/group/eventmachine).
Also, EventMachine supports TLS (aka SSL) and works well on [JRuby](http://jruby.org) and Windows.
Learn more in our {file:docs/Ecosystem.md EventMachine ecosystem} and {file:docs/TLS.md TLS (aka SSL)} guides.
#### Connection loss detection ####
Finally, connection loss detection. When our chat participant closes her laptop lid, how do we know that she is no longer active? The answer is, when EventMachine
detects TCP connectin closure, it calls {EventMachine::Connection#unbind}. Version 1.0.beta3 and later also pass an optional argument to that method. The argument
indicates what error (if any) caused the connection to be closed.
Learn more in our {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery} guide.
#### What the Chat Server Example doesn't demonstrate ####
This chat server also leaves out something production quality clients and servers must take care of: buffering. We intentionally did not include any buffering in
our chat server example: it would only distract you from learning what you really came here to learn: how to use EventMachine to build blazing fast asynchronous
networking programs quickly. However, {EventMachine::Connection#receive_data} does not offer any guarantees that you will be receiving "whole messages" all the time,
largely because the underlying transport (UDP or TCP) does not offer such guarantees. Many protocols, for example, AMQP, mandate that large content chunks are
split into smaller _frames_ of certain size. This means that [amq-client](https://github.com/ruby-amqp/amq-client) library, for instance, that has EventMachine-based driver,
has to deal with figuring out when exactly we received "the whole message". To do so, it uses buffering and employs various checks to detect _frame boundaries_.
So **don't be deceived by the simplicity of this chat example**: it intentionally leaves framing out, but real world protocols usually require it.
## A (Proxying) Chat Client Example ##
TBD
## Wrapping up ##
This tutorial ends here. Congratulations! You have learned quite a bit about EventMachine.
## What to read next ##
The documentation is organized as a {file:docs/DocumentationGuidesIndex.md number of guides}, covering all kinds of
topics. TBD
## Tell us what you think! ##
Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3)
or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered?
Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is
key to making documentation better.
@@ -0,0 +1,211 @@
01Oct06: Replaced EventMachine#open_datagram_server with a version that can
take a Class or a Module, instead of just a Module. Thanks to Tobias
Gustafsson for pointing out the missing case.
04Oct06: Supported subsecond timer resolutions, per request by Jason Roelofs.
05Oct06: Added EventMachine#set_quantum, which sets the timer resolution.
15Nov06: Added Connection#set_comm_inactivity_timeout.
15Nov06: Checked in a Line-and-Text Protocol Handler.
18Nov06: Checked in a Header-and-Body Protocol Handler.
22Nov06: Changed EventMachine#reconnect: no longer excepts when called on an
already-connected handler.
28Nov06: Supported a binary-unix gem.
19Dec06: Added EventMachine#set_effective_user.
05Jan07: Upped max outstanding timers to 1000.
15May07: Applied Solaris patches from Brett Eisenberg
22May07: Cleaned up the license text in all the source files.
22May07: Released version 0.7.2
23May07: Per suggestion from Bill Kelly, fixed a bug with the initialization
of the network libraries under Windows. The goal is to enable EM to
be used without Ruby.
28May07: Applied patch from Bill Kelly, refactors the declarations of
event names to make EM easier to use from C programs without Ruby.
31May07: Added a preliminary implementation of EventMachine#popen.
01Jun07: Added EM, a "pseudo-alias" for EventMachine.
01Jun07: Added EM#next_tick.
01Jun07: Added EM::Connection#get_outbound_data_size
05Jun07: Removed the code which loads a pure-Ruby EM library in case the
compiled extension is unavailable. Suggested by Moshe Litvin.
06Jun07: Preliminary epoll implementation.
12Jun07: Added an evented popen implementation that, like Ruby's, is
full-duplex and makes the subprocess PID available to the caller.
06Jul07: Performance-tweaked the callback dispatcher in eventmachine.rb.
10Jul07: Released version 0.8.0.
12Jul07: Applied patches from Tim Pease to fix Solaris build problems.
15Jul07: Created a new provisional source branch, experiments/jruby-1.
This is a preliminary implementation of the EM reactor in Java,
suitable for use with JRuby.
17Jul07: Added EventMachine#stop_server, per request from Kirk Haines,
and associated unit tests.
22Jul07: Added EventMachine#stream_file_data. This is a very fast and scalable
way of sending data from static files over network connections. It
has separate implementations for small files and large file, and
has tunings to minimize memory consumption.
26Jul07: Added some patches by Kirk Haines to improve the behavior of
EM::Connection#send_file_data_to_connection.
26Jul07: Added a C++ module for directly integrating EM into C++ programs
with no Ruby dependencies. Needs example code.
29Jul07: Added EventMachine::Protocols::LineText2.
29Jul07: Added EventMachine::Protocols::Stomp.
30Jul07: Added sys/stat.h to project.h to fix compilation bug on Darwin.
13Aug07: Added EventMachine#reactor_running?
15Aug07: Added parameters for EventMachine::Connection:start_tls that can be
used to specify client-side private keys and certificates.
17Aug07: Added EventMachine#run_block, a sugaring for a common use case.
24Aug07: Added a preliminary keyboard handler. Needs docs and testing on
windows.
26Aug07: Created EventMachine::Spawnable, an implementation of Erlang-like
processes.
27Aug07: Silenced some -w warnings, requested by James Edward Gray II.
30Aug07: Added cookies to EM::HttpClient#request.
04Sep07: Added an initial implementation of an evented SMTP client.
04Sep07: Added an initial implementation of an evented SMTP server.
10Sep07: Changed EM#spawn to run spawned blocks in the context of the
SpawnedProcess object, not of whatever was the active object at the
time of the spawn.
14Sep07: Heartbeats weren't working with EPOLL. Noticed by Brian Candler.
15Sep07: Added some features, tests and documents to Deferrable.
16Sep07: Added [:content] parameter to EM::Protocols::SmtpClient#send.
16Sep07: Bumped version to 0.9.0 in anticipation of a release.
18Sep07: Released version 0.9.0.
19Sep07: Added #receive_reset to EM::Protocols::SmtpServer.
19Sep07: User overrides of EM::Protocols::SmtpServer#receive_recipient can now
return a Deferrable. Also fixed bug: SmtpClient now raises a protocol
error if none of its RCPT TO: commands are accepted by the server.
26Sep07: Fixed missing keyboard support for Windows.
03Oct07: Added a default handler for RuntimeErrors emitted from user-written
code. Suggested by Brian Candler.
19Oct07: Set the SO_BROADCAST option automatically on all UDP sockets.
10Nov07: Forced integer conversion of send_datagram's port parameter.
Suggested by Matthieu Riou.
12Nov07: Added saslauth.rb, a protocol module to replace the Cyrus SASL
daemons saslauthd and pwcheck.
15Nov07: Fixed bug reported by Mark Zvillius. We were failing to dispatch
zero-length datagrams under certain conditions.
19Nov07: Added EventMachine#set_max_timers. Requested by Matthieu Riou and
others.
19Nov07: Fixed bug with EM::Connection#start_tls. Was not working with server
connections. Reported by Michael S. Fischer.
26Nov07: Supported a hack for EventMachine#popen so it can return an exit
status from subprocesses. Requested by Michael S. Fischer.
30Nov07: Changed Pipe descriptors so that the child-side of the socketpair is
NOT set nonblocking. Suggested by Duane Johnson.
05Dec07: Re-enabled the pure-Ruby implementation.
06Dec07: Released Version 0.10.0.
13Dec07: Added EM::DeferrableChildProcess
24Dec07: Added a SASL client for simple password authentication.
27Dec07: Removed the hookable error handler. No one was using it and it significantly
degraded performance.
30Dec07: Implemented Kqueue support for OSX and BSD.
04Jan08: Fixed bug in epoll ("Bad file descriptor"), patch supplied by Chris
Heath.
04Jan08: Fixed bug reported by Michael S. Fischer. We were terminating
SSL connections that sent data before the handshake was complete.
08Jan08: Added an OpenBSD branch for extconf.rb, contributed by Guillaume
Sellier.
19Jan08: Added EM::Connection::get_sockname per request by Michael Fischer.
19Jan08: Supported IPv6 addresses.
30Apr08: Set the NODELAY option on sockets that we connect to other servers.
Omission noted by Roger Pack.
14May08: Generated a 0.12 release.
15May08: Supported EM#get_sockname for acceptors (TCP server sockets).
Requested by Roger Pack.
15May08; Accepted a patch from Dan Aquino that allows the interval of a
PeriodicTimer to be changed on the fly.
15Jun08: Supported nested calls to EM#run. Many people contributed ideas to
this, notably raggi and tmm1.
20Jul08: Accepted patch from tmm1 for EM#fork_reactor.
28Jul08: Added a Postgres3 implementation, written by FCianfrocca.
14Aug08: Added a patch by Mike Murphy to support basic auth in the http
client.
28Aug08: Added a patch by tmm1 to fix a longstanding problem with Java
data-sends.
13Sep08: Added LineText2#set_binary_mode, a back-compatibility alias.
13Sep08: Modified the load order of protocol libraries in eventmachine.rb
to permit a modification of HeaderAndContentProtocol.
13Sep08: Modified HeaderAndContent to use LineText2, which is less buggy
than LineAndTextProtocol. This change may be reversed if we can fix
the bugs in buftok.
13Sep08: Improved the password handling in the Postgres protocol handler.
15Sep08: Added attach/detach, contributed by Aman Gupta (tmm1) and Riham Aldakkak,
to support working with file descriptors not created in the reactor.
16Sep08: Added an optional version string to the HTTP client. This is a hack
that allows a client to specify a version 1.0 request, which
keeps the server from sending a chunked response. The right way to
solve this, of course, is to support chunked responses.
23Sep08: ChangeLog Summary for Merge of branches/raggi
Most notable work and patches by Aman Gupta, Roger Pack, and James Tucker.
Patches / Tickets also submitted by: Jeremy Evans, aanand, darix, mmmurf,
danielaquino, macournoyer.
- Moved docs into docs/ dir
- Major refactor of rakefile, added generic rakefile helpers in tasks
- Added example CPP build rakefile in tasks/cpp.rake
- Moved rake tests out to tasks/tests.rake
- Added svn ignores where appropriate
- Fixed jruby build on older java platforms
- Gem now builds from Rakefile rather than directly via extconf
- Gem unified for jruby, C++ and pure ruby.
- Correction for pure C++ build, removing ruby dependency
- Fix for CYGWIN builds on ipv6
- Major refactor for extconf.rb
- Working mingw builds
- extconf optionally uses pkg_config over manual configuration
- extconf builds for 1.9 on any system that has 1.9
- extconf no longer links pthread explicitly
- looks for kqueue on all *nix systems
- better error output on std::runtime_error, now says where it came from
- Fixed some tests on jruby
- Added test for general send_data flaw, required for a bugfix in jruby build
- Added timeout to epoll tests
- Added fixes for java reactor ruby api
- Small addition of some docs in httpclient.rb and httpcli2.rb
- Some refactor and fixes in smtpserver.rb
- Added parenthesis where possible to avoid excess ruby warnings
- Refactor of $eventmachine_library logic for accuracy and maintenance, jruby
- EM::start_server now supports unix sockets
- EM::connect now supports unix sockets
- EM::defer @threadqueue now handled more gracefully
- Added better messages on exceptions raised
- Fix edge case in timer fires
- Explicitly require buftok.rb
- Add protocols to autoload, rather than require them all immediately
- Fix a bug in pr_eventmachine for outbound_q
- Refactors to take some of the use of defer out of tests.
- Fixes in EM.defer under start/stop conditions. Reduced scope of threads.
23Sep08: Added patch from tmm1 to avoid popen errors on exit.
30Sep08: Added File.exists? checks in the args for start_tls, as suggested by
Brian Lopez (brianmario).
10Nov08: ruby 1.9 compatibility enhancements
28Nov08: Allow for older ruby builds where RARRAY_LEN is not defined
03Dec08: allow passing arguments to popen handlers
13Jan09: SSL support for httpclient2 (David Smalley)
22Jan09: Fixed errors on OSX with the kqueue reactor, fixed errors in the pure
ruby reactor. Added EM.current_time. Added EM.epoll? and EM.kqueue?
27Jan09: Reactor errors are now raised as ruby RuntimeErrors.
28Jan09: Documentation patch from alloy
29Jan09: (Late sign-off) Use a longer timeout for connect_server (Ilya
Grigorik)
07Feb09: Fix signal handling issues with threads+epoll
07Feb09: Use rb_thread_schedule in the epoll reactor
07Feb09: Use TRAP_BEG/END and rb_thread_schedule in kqueue reactor
08Feb09: Added fastfilereader from swiftiply
08Feb09: 1.9 fix for rb_trap_immediate
08Feb09: Enable rb_thread_blocking_region for 1.9.0 and 1.9.1
10Feb09: Support win32 builds for fastfilereader
10Feb09: Added a new event to indicate completion of SSL handshake on TCP
connections
10Feb09: Working get_peer_cert method. Returns the certificate as a Ruby
String in PEM format. (Jake Douglas)
10Feb09: Added EM.get_max_timers
11Feb09: Fix compile options for sun compiler (Alasdairrr)
11Feb09: get_status returns a Process::Status object
12Feb09: Add EM::Protocols::Memcache with simple get/set functionality
19Feb09: Add catch-all EM.error_handler
20Feb09: Support miniunit (1.9)
20Feb09: Return success on content-length = 0 instead of start waiting forever
(Ugo Riboni)
25Feb09: Allow next_tick to be used to pre-schedule reactor operations before
EM.run
26Feb09: Added EM.get_connection_count
01Mar09: Switch back to extconf for compiling gem extensions
01Mar09: fixed a small bug with basic auth (mmmurf)
@@ -0,0 +1,246 @@
EventMachine (EM) adds two different formalisms for lightweight concurrency
to the Ruby programmer's toolbox: spawned processes and deferrables. This
note will show you how to use deferrables. For more information, see the
separate document LIGHTWEIGHT_CONCURRENCY.
=== What are Deferrables?
EventMachine's Deferrable borrows heavily from the "deferred" object in
Python's "Twisted" event-handling framework. Here's a minimal example that
illustrates Deferrable:
require 'eventmachine'
class MyClass
include EM::Deferrable
def print_value x
puts "MyClass instance received #{x}"
end
end
EM.run {
df = MyClass.new
df.callback {|x|
df.print_value(x)
EM.stop
}
EM::Timer.new(2) {
df.set_deferred_status :succeeded, 100
}
}
This program will spin for two seconds, print out the string "MyClass
instance received 100" and then exit. The Deferrable pattern relies on
an unusual metaphor that may be unfamiliar to you, unless you've used
Python's Twisted. You may need to read the following material through
more than once before you get the idea.
EventMachine::Deferrable is simply a Ruby Module that you can include
in your own classes. (There also is a class named
EventMachine::DefaultDeferrable for when you want to create one without
including it in code of your own.)
An object that includes EventMachine::Deferrable is like any other Ruby
object: it can be created whenever you want, returned from your functions,
or passed as an argument to other functions.
The Deferrable pattern allows you to specify any number of Ruby code
blocks (callbacks or errbacks) that will be executed at some future time
when the status of the Deferrable object changes.
How might that be useful? Well, imagine that you're implementing an HTTP
server, but you need to make a call to some other server in order to fulfill
a client request.
When you receive a request from one of your clients, you can create and
return a Deferrable object. Some other section of your program can add a
callback to the Deferrable that will cause the client's request to be
fulfilled. Simultaneously, you initiate an event-driven or threaded client
request to some different server. And then your EM program will continue to
process other events and service other client requests.
When your client request to the other server completes some time later, you
will call the #set_deferred_status method on the Deferrable object, passing
either a success or failure status, and an arbitrary number of parameters
(which might include the data you received from the other server).
At that point, the status of the Deferrable object becomes known, and its
callback or errback methods are immediately executed. Callbacks and errbacks
are code blocks that are attached to Deferrable objects at any time through
the methods #callback and #errback.
The deep beauty of this pattern is that it decouples the disposition of one
operation (such as a client request to an outboard server) from the
subsequent operations that depend on that disposition (which may include
responding to a different client or any other operation).
The code which invokes the deferred operation (that will eventually result
in a success or failure status together with associated data) is completely
separate from the code which depends on that status and data. This achieves
one of the primary goals for which threading is typically used in
sophisticated applications, with none of the nondeterminacy or debugging
difficulties of threads.
As soon as the deferred status of a Deferrable becomes known by way of a call
to #set_deferred_status, the Deferrable will IMMEDIATELY execute all of its
callbacks or errbacks in the order in which they were added to the Deferrable.
Callbacks and errbacks can be added to a Deferrable object at any time, not
just when the object is created. They can even be added after the status of
the object has been determined! (In this case, they will be executed
immediately when they are added.)
A call to Deferrable#set_deferred_status takes :succeeded or :failed as its
first argument. (This determines whether the object will call its callbacks
or its errbacks.) #set_deferred_status also takes zero or more additional
parameters, that will in turn be passed as parameters to the callbacks or
errbacks.
In general, you can only call #set_deferred_status ONCE on a Deferrable
object. A call to #set_deferred_status will not return until all of the
associated callbacks or errbacks have been called. If you add callbacks or
errbacks AFTER making a call to #set_deferred_status, those additional
callbacks or errbacks will execute IMMEDIATELY. Any given callback or
errback will be executed AT MOST once.
It's possible to call #set_deferred_status AGAIN, during the execution a
callback or errback. This makes it possible to change the parameters which
will be sent to the callbacks or errbacks farther down the chain, enabling
some extremely elegant use-cases. You can transform the data returned from
a deferred operation in arbitrary ways as needed by subsequent users, without
changing any of the code that generated the original data.
A call to #set_deferred_status will not return until all of the associated
callbacks or errbacks have been called. If you add callbacks or errbacks
AFTER making a call to #set_deferred_status, those additional callbacks or
errbacks will execute IMMEDIATELY.
Let's look at some more sample code. It turns out that many of the internal
protocol implementations in the EventMachine package rely on Deferrable. One
of these is EM::Protocols::HttpClient.
To make an evented HTTP request, use the module function
EM::Protocols::HttpClient#request, which returns a Deferrable object.
Here's how:
require 'eventmachine'
EM.run {
df = EM::Protocols::HttpClient.request( :host=>"www.example.com",
:request=>"/index.html" )
df.callback {|response|
puts "Succeeded: #{response[:content]}"
EM.stop
}
df.errback {|response|
puts "ERROR: #{response[:status]}"
EM.stop
}
}
(See the documentation of EventMachine::Protocols::HttpClient for information
on the object returned by #request.)
In this code, we make a call to HttpClient#request, which immediately returns
a Deferrable object. In the background, an HTTP client request is being made
to www.example.com, although your code will continue to run concurrently.
At some future point, the HTTP client request will complete, and the code in
EM::Protocols::HttpClient will process either a valid HTTP response (including
returned content), or an error.
At that point, EM::Protocols::HttpClient will call
EM::Deferrable#set_deferred_status on the Deferrable object that was returned
to your program, as the return value from EM::Protocols::HttpClient.request.
You don't have to do anything to make this happen. All you have to do is tell
the Deferrable what to do in case of either success, failure, or both.
In our code sample, we set one callback and one errback. The former will be
called if the HTTP call succeeds, and the latter if it fails. (For
simplicity, we have both of them calling EM#stop to end the program, although
real programs would be very unlikely to do this.)
Setting callbacks and errbacks is optional. They are handlers to defined
events in the lifecycle of the Deferrable event. It's not an error if you
fail to set either a callback, an errback, or both. But of course your
program will then fail to receive those notifications.
If through some bug it turns out that #set_deferred_status is never called
on a Deferrable object, then that object's callbacks or errbacks will NEVER
be called. It's also possible to set a timeout on a Deferrable. If the
timeout elapses before any other call to #set_deferred_status, the Deferrable
object will behave as is you had called set_deferred_status(:failed) on it.
Now let's modify the example to illustrate some additional points:
require 'eventmachine'
EM.run {
df = EM::Protocols::HttpClient.request( :host=>"www.example.com",
:request=>"/index.html" )
df.callback {|response|
df.set_deferred_status :succeeded, response[:content]
}
df.callback {|string|
puts "Succeeded: #{string}"
EM.stop
}
df.errback {|response|
puts "ERROR: #{response[:status]}"
EM.stop
}
}
Just for the sake of illustration, we've now set two callbacks instead of
one. If the deferrable operation (the HTTP client-request) succeeds, then
both of the callbacks will be executed in order.
But notice that we've also made our own call to #set_deferred_status in the
first callback. This isn't required, because the HttpClient implementation
already made a call to #set_deferred_status. (Otherwise, of course, the
callback would not be executing.)
But we used #set_deferred_status in the first callback in order to change the
parameters that will be sent to subsequent callbacks in the chain. In this
way, you can construct powerful sequences of layered functionality. If you
want, you can even change the status of the Deferrable from :succeeded to
:failed, which would abort the chain of callback calls, and invoke the chain
of errbacks instead.
Now of course it's somewhat trivial to define two callbacks in the same
method, even with the parameter-changing effect we just described. It would
be much more interesting to pass the Deferrable to some other function (for
example, a function defined in another module or a different gem), that would
in turn add callbacks and/or errbacks of its own. That would illustrate the
true power of the Deferrable pattern: to isolate the HTTP client-request
from other functions that use the data that it returns without caring where
those data came from.
Remember that you can add a callback or an errback to a Deferrable at any
point in time, regardless of whether the status of the deferred operation is
known (more precisely, regardless of when #set_deferred_status is called on
the object). Even hours or days later.
When you add a callback or errback to a Deferrable object on which
#set_deferred_status has not yet been called, the callback/errback is queued
up for future execution, inside the Deferrable object. When you add a
callback or errback to a Deferrable on which #set_deferred_status has
already been called, the callback/errback will be executed immediately.
Your code doesn't have to worry about the ordering, and there are no timing
issues, as there would be with a threaded approach.
For more information on Deferrables and their typical usage patterns, look
in the EM unit tests. There are also quite a few sugarings (including
EM::Deferrable#future) that make typical Deferrable usages syntactically
easier to work with.
+141
View File
@@ -0,0 +1,141 @@
EventMachine now supports epoll, bringing large increases in performance and scalability to Ruby programs.
Epoll(7) is a alternative mechanism for multiplexed I/O that is available in Linux 2.6 kernels.
It features significantly greater performance than the standard select(2) mechanism, when used in
applications that require very large numbers of open I/O descriptors.
EventMachine has always used select(2) because its behavior is well standardized and broadly supported.
But select becomes unreasonably slow when a program has a
very large number of file descriptors or sockets. Ruby's version of select hardcodes a limit
of 1024 descriptors per process, but heavily loaded processes will start to show performance
degradation even after only a few hundred descriptors are in use.
Epoll is an extended version of the poll(2) call, and it solves the problems with select. Programs
based on epoll can easily scale past Ruby's 1024-descriptor limit, potentially to tens of thousands
of connectors, with no significant impact on performance.
(Another alternative which is very similar to epoll in principle is kqueue, supplied on BSD and its
variants.)
This note shows you how to use epoll in your programs.
=== Compiling EventMachine to use epoll.
You don't have to do anything to get epoll support in EventMachine.
When you compile EventMachine on a platform that supports epoll, EM will
automatically generate a Makefile that includes epoll. (At this writing, this will only work
on Linux 2.6 kernels.) If you compile EM on a platform without epoll, then epoll support will
be omitted from the Makefile, and EM will work just as it always has.
=== Using epoll in your programs.
First, you need to tell EventMachine to use epoll instead of select (but see below, as this requirement
will be removed in a future EventMachine version). Second, you need to prepare your program to use
more than 1024 descriptors, an operation that generally requires superuser privileges. Third, you will probably
want your process to drop the superuser privileges after you increase your process's descriptor limit.
=== Using EventMachine#epoll
Call the method EventMachine#epoll anytime before you call EventMachine#run, and your program will
automatically use epoll, if available. It's safe to call EventMachine#epoll on any platform because
it compiles to a no-op on platforms that don't support epoll.
require 'rubygems'
require 'eventmachine'
EM.epoll
EM.run {
...
}
EventMachine#epoll was included in this initial release only to avoid changing the behavior of existing
programs. However, it's expected that a future release of EM will convert EventMachine#epoll to a no-op,
and run epoll by default on platforms that support it.
=== Using EventMachine#set_descriptor_table_size
In Linux (as in every Unix-like platform), every process has a internal table that determines the maximum
number of file and socket descriptors you may have open at any given time. The size of this table is
generally fixed at 1024, although it may be increased within certain system-defined hard and soft limits.
If you want your EventMachine program to support more than 1024 total descriptors, you must use
EventMachine#set_descriptor_table_size, as follows:
require 'rubygems'
require 'eventmachine'
new_size = EM.set_descriptor_table_size( 60000 )
$>.puts "New descriptor-table size is #{new_size}"
EM.run {
...
}
If successful, this example will increase the maximum number of descriptors that epoll can use to 60,000.
Call EventMachine#set_descriptor_table_size without an argument at any time to find out the current
size of the descriptor table.
Using EventMachine#set_descriptor_table_size ONLY affects the number of descriptors that can be used
by epoll. It has no useful effect on platforms that don't support epoll, and it does NOT increase the
number of descriptors that Ruby's own I/O functions can use.
#set_descriptor_table_size can fail if your process is not running as superuser, or if you try to set a
table size that exceeds the hard limits imposed by your system. In the latter case, try a smaller number.
=== Using EventMachine#set_effective_user
In general, you must run your program with elevated or superuser privileges if you want to increase
your descriptor-table size beyond 1024 descriptors. This is easy enough to verify. Try running the
sample program given above, that increases the descriptor limit to 60,000. You will probably find that
the table size will not be increased if you don't run your program as root or with elevated privileges.
But of course network servers, especially long-running ones, should not run with elevated privileges.
You will want to drop superuser privileges as soon as possible after initialization. To do this,
use EventMachine#set_effective_user:
require 'rubygems'
require 'eventmachine'
# (Here, program is running as superuser)
EM.set_descriptor_table_size( 60000 )
EM.set_effective_user( "nobody" )
# (Here, program is running as nobody)
EM.run {
...
}
Of course, you will need to replace "nobody" in the example with the name of an unprivileged user
that is valid on your system. What if you want to drop privileges after opening a server socket
on a privileged (low-numbered) port? Easy, just call #set_effective_user after opening your sockets:
require 'rubygems'
require 'eventmachine'
# (Here, program is running as superuser)
EM.set_descriptor_table_size( 60000 )
EM.run {
EM.start_server( "0.0.0.0", 80, MyHttpServer )
EM.start_server( "0.0.0.0", 443, MyEncryptedHttpServer )
EM.set_effective_user( "nobody" )
# (Here, program is running as nobody)
...
}
Because EventMachine#set_effective_user is used to enforce security
requirements, it has no nonfatal errors. If you try to set a nonexistent or invalid effective user,
#set_effective_user will abort your program, rather than continue to run with elevated privileges.
EventMachine#set_effective_user is a silent no-op on platforms that don't support it, such as Windows.
@@ -0,0 +1,13 @@
If you have obtained an EventMachine source-tarball (.tar.gz):
unzip and untar the tarball, and enter the directory that is
created. In that directory, say:
ruby setup.rb
(You may need to be root to execute this command.)
To create documentation for EventMachine, simply type:
rake rdoc
in the distro directory. Rdocs will be created in subdirectory rdoc.
If you have obtained a gem version of EventMachine, install it in the
usual way (gem install eventmachine). You may need superuser privileges
to execute this command.
@@ -0,0 +1,42 @@
EventMachine (EM) can respond to keyboard events. This gives your event-driven
programs the ability to respond to input from local users.
Programming EM to handle keyboard input in Ruby is simplicity itself. Just use
EventMachine#open_keyboard, and supply the name of a Ruby module or class that
will receive the input:
require 'rubygems'
require 'eventmachine'
module MyKeyboardHandler
def receive_data keystrokes
puts "I received the following data from the keyboard: #{keystrokes}"
end
end
EM.run {
EM.open_keyboard(MyKeyboardHandler)
}
If you want EM to send line-buffered keyboard input to your program, just
include the LineText2 protocol module in your handler class or module:
require 'rubygems'
require 'eventmachine'
module MyKeyboardHandler
include EM::Protocols::LineText2
def receive_line data
puts "I received the following line from the keyboard: #{data}"
end
end
EM.run {
EM.open_keyboard(MyKeyboardHandler)
}
As we said, simplicity itself. You can call EventMachine#open_keyboard at any
time while the EM reactor loop is running. In other words, the method
invocation may appear anywhere in an EventMachine#run block, or in any code
invoked in the #run block.
@@ -0,0 +1,25 @@
LEGAL NOTICE INFORMATION
------------------------
EventMachine is Copyright (C) 2006-07 by Francis Cianfrocca.
EventMachine is copyrighted software owned by Francis Cianfrocca
(blackhedd ... gmail.com). You may redistribute and/or modify this
software as long as you comply with either the terms of the GPL
(see the file GPL), or Ruby's license (see the file COPYING).
Your use of all the files in this distribution is controlled by these
license terms, except for those files specifically mentioned below:
setup.rb
This file is Copyright (C) 2000-2005 by Minero Aoki
You can distribute/modify this file under the terms of
the GNU LGPL, Lesser General Public License version 2.1.
lib/em/buftok.rb
This file is Copyright (C) 2007 by Tony Arcieri. This file is
covered by the terms of Ruby's License (see the file COPYING).
@@ -0,0 +1,130 @@
EventMachine (EM) adds two different formalisms for lightweight concurrency to
the Ruby programmer's toolbox: spawned processes and deferrables. This note
will show you how to use them.
=== What is Lightweight Concurrency?
We use the term "Lightweight Concurrency" (LC) to refer to concurrency
mechanisms that are lighter than Ruby threads. By "lighter," we mean: less
resource-intensive in one or more dimensions, usually including memory and
CPU usage. In general, you turn to LC in the hope of improving the
performance and scalability of your programs.
In addition to the two EventMachine mechanisms we will discuss here, Ruby
has at least one other LC construct: Fibers, which are currently under
development in Ruby 1.9.
The technical feature that makes all of these LC mechanisms different from
standard Ruby threads is that they are not scheduled automatically.
When you create and run Ruby threads, you can assume (within certain
constraints) that your threads will all be scheduled fairly by Ruby's runtime.
Ruby itself is responsible for giving each of your threads its own share of
the total runtime.
But with LC, your program is responsible for causing different execution
paths to run. In effect, your program has to act as a "thread scheduler."
Scheduled entities in LC run to completion and are never preempted. The
runtime system has far less work to do since it has no need to interrupt
threads or to schedule them fairly. This is what makes LC lighter and faster.
You'll learn exactly how LC scheduling works in practice as we work through
specific examples.
=== EventMachine Lightweight Concurrency
Recall that EM provides a reactor loop that must be running in order for
your programs to perform event-driven logic. An EM program typically has a
structure like this:
require 'eventmachine'
# your initializations
EM.run {
# perform event-driven I/O here, including network clients,
# servers, timers, and thread-pool operations.
}
# your cleanup
# end of the program
EventMachine#run executes the reactor loop, which causes your code to be
called as events of interest to your program occur. The block you pass to
EventMachine#run is executed right after the reactor loop starts, and is
the right place to start socket acceptors, etc.
Because the reactor loop runs constantly in an EM program (until it is
stopped by a call to EventMachine#stop), it has the ability to schedule
blocks of code for asynchronous execution. Unlike a pre-emptive thread
scheduler, it's NOT able to interrupt code blocks while they execute. But
the scheduling capability it does have is enough to enable lightweight
concurrency.
For information on Spawned Processes, see the separate document
SPAWNED_PROCESSES.
For information on Deferrables, see the separate document DEFERRABLES.
=== [SIDEBAR]: I Heard That EventMachine Doesn't Work With Ruby Threads.
This is incorrect. EM is fully interoperable with all versions of Ruby
threads, and has been since its earliest releases.
It's very true that EM encourages an "evented" (non-threaded) programming
style. The specific benefits of event-driven programming are far better
performance and scalability for well-written programs, and far easier
debugging.
The benefit of using threads for similar applications is a possibly more
intuitive programming model, as well as the fact that threads are already
familiar to most programmers. Also, bugs in threaded programs often fail
to show up until programs go into production. These factors create the
illusion that threaded programs are easier to write.
However, some operations that occur frequently in professional-caliber
applications simply can't be done without threads. (The classic example
is making calls to database client-libraries that block on network I/O
until they complete.)
EventMachine not only allows the use of Ruby threads in these cases, but
it even provides a built-in thread-pool object to make them easier to
work with.
You may have heard a persistent criticism that evented I/O is fundamentally
incompatible with Ruby threads. It is true that some well-publicized attempts
to incorporate event-handling libraries into Ruby were not successful. But
EventMachine was designed from the ground up with Ruby compatibility in mind,
so EM never suffered from the problems that defeated the earlier attempts.
=== [SIDEBAR]: I Heard That EventMachine Doesn't Work Very Well On Windows.
This too is incorrect. EventMachine is an extension written in C++ and Java,
and therefore it requires compilation. Many Windows computers (and some Unix
computers, especially in production environments) don't have a build stack.
Attempting to install EventMachine on a machine without a compiler usually
produces a confusing error.
In addition, Ruby has a much-debated issue with Windows compiler versions.
Ruby on Windows works best with Visual Studio 6, a compiler version that is
long out-of-print, no longer supported by Microsoft, and difficult to obtain.
(This problem is not specific to EventMachine.)
Shortly after EventMachine was first released, the compiler issues led to
criticism that EM was incompatible with Windows. Since that time, every
EventMachine release has been supplied in a precompiled binary form for
Windows users, that does not require you to compile the code yourself. EM
binary Gems for Windows are compiled using Visual Studio 6.
EventMachine does supply some advanced features (such as Linux EPOLL support,
reduced-privilege operation, UNIX-domain sockets, etc.) that have no
meaningful implementation on Windows. Apart from these special cases, all EM
functionality (including lightweight concurrency) works perfectly well on
Windows.
@@ -0,0 +1,75 @@
EventMachine is supplied in three alternative versions.
1) A version that includes a Ruby extension written in C++. This version requires compilation;
2) A version for JRuby that contains a precompiled JAR file written in Java;
3) A pure Ruby version that has no external dependencies and can run in any Ruby environment.
The Java version of EventMachine is packaged in a distinct manner and must be installed using a
special procedure. This version is described fully in a different document, and not considered
further here.
The C++ and pure-Ruby versions, however, are shipped in the same distribution. You use the same
files (either tarball or Ruby gem) to install both of these versions.
If you intend to use the C++ version, you must successfully compile EventMachine after you install it.
(The gem installation attempts to perform this step automatically.)
If you choose not to compile the EventMachine C++ extension, or if your compilation fails for any
reason, you still have a fully-functional installation of the pure-Ruby version of EM.
However, for technical reasons, a default EM installation (whether or not the compilation succeeds)
will always assume that the compiled ("extension") implementation should be used.
If you want your EM program to use the pure Ruby version, you must specifically request it. There
are two ways to do this: by setting either a Ruby global variable, or an environment string.
The following code will invoke the pure-Ruby implementation of EM:
$eventmachine_library = :pure_ruby
require 'eventmachine'
EM.library_type #=> "pure_ruby"
Notice that this requires a code change and is not the preferred way to select pure Ruby, unless
for some reason you are absolutely sure you will never want the compiled implementation.
Setting the following environment string has the same effect:
export EVENTMACHINE_LIBRARY="pure_ruby"
This technique gives you the flexibility to select either version at runtime with no code changes.
Support
The EventMachine development team has committed to support precisely the same APIs for all the
various implementations of EM.
This means that you can expect any EM program to behave identically, whether you use pure Ruby,
the compiled C++ extension, or JRuby. Deviations from this behavior are to be considered bugs
and should be reported as such.
There is a small number of exceptions to this rule, which arise from underlying platform
distinctions. Notably, EM#epoll is a silent no-op in the pure Ruby implementation.
When Should You Use the Pure-Ruby Implementation of EM?
Use the pure Ruby implementation of EM when you must support a platform for which no C++ compiler
is available, or on which the standard EM C++ code can't be compiled.
Keep in mind that you don't need a C++ compiler in order to deploy EM applications that rely on
the compiled version, so long as appropriate C++ runtime libraries are available on the target platform.
In extreme cases, you may find that you can develop software with the compiled EM version, but are
not allowed to install required runtime libraries on the deployment system(s). This would be another
case in which the pure Ruby implementation can be useful.
In general you should avoid the pure Ruby version of EM when performance and scalability are important.
EM in pure Ruby will necessarily run slower than the compiled version. Depending on your application
this may or may not be a key issue.
Also, since EPOLL is not supported in pure Ruby, your applications will be affected by Ruby's built-in
limit of 1024 file and socket descriptors that may be open in a single process. For maximum scalability
and performance, always use EPOLL if possible.
@@ -0,0 +1,94 @@
RUBY/EventMachine RELEASE NOTES
--------------------------------------------------
Version: 0.9.0, released xxXXX07
Added Erlang-like distributed-computing features
--------------------------------------------------
Version: 0.8.0, released 23Jun07
Added an epoll implementation for Linux 2.6 kernels.
Added evented #popen.
--------------------------------------------------
Version: 0.7.3, released 22May07
Added a large variety of small features. See the ChangeLog.
--------------------------------------------------
Version: 0.7.1, released xxNov06
Added protocol handlers for line-oriented protocols.
Various bug fixes.
--------------------------------------------------
Version: 0.7.0, released 20Nov06
Added a fix in em.cpp/ConnectToServer to fix a fatal exception that
occurred in FreeBSD when connecting successfully to a remote server.
--------------------------------------------------
Version: 0.6.0, released xxJul06
Added deferred operations, suggested by Don Stocks, amillionhitpoints@yahoo.com.
--------------------------------------------------
Version: 0.5.4, released xxJun06
Added get_peername support for streams and datagrams.
--------------------------------------------------
Version: 0.5.3, released 17May06
Fixed bugs in extconf.rb, thanks to Daniel Harple, dharple@generalconsumption.org.
Added proper setup.rb and rake tasks, thanks to Austin Ziegler.
Fixed a handful of reported problems with builds on various platforms.
--------------------------------------------------
Version: 0.5.2, released 05May06
Made several nonvisible improvements to the Windows
implementation.
Added an exception-handling patch contributed by Jeff Rose, jeff@rosejn.net.
Added a dir-config patch contributed anonymously.
Supported builds on Solaris.
--------------------------------------------------
Version: 0.5.1, released 05May06
Made it possible to pass a Class rather than a Module
to a protocol handler.
Added Windows port.
--------------------------------------------------
Version: 0.5.0, released 30Apr06
Added a preliminary SSL/TLS extension. This will probably
change over the next few releases.
--------------------------------------------------
Version: 0.4.5, released 29Apr06
Changed ext files so the ruby.h is installed after unistd.h
otherwise it doesn't compile on gcc 4.1
--------------------------------------------------
Version: 0.4.2, released 19Apr06
Changed the Ruby-glue so the extension will play nicer
in the sandbox with Ruby threads.
Added an EventMachine::run_without_threads API to
switch off the thread-awareness for better performance
in programs that do not spin any Ruby threads.
--------------------------------------------------
Version: 0.4.1, released 15Apr06
Reworked the shared-object interface to make it easier to
use EventMachine from languages other than Ruby.
--------------------------------------------------
Version: 0.3.2, released 12Apr06
Added support for a user-supplied block in EventMachine#connect.
--------------------------------------------------
Version: 0.3.1, released 11Apr06
Fixed bug that prevented EventMachine from being run multiple
times in a single process.
--------------------------------------------------
Version: 0.3.0, released 10Apr06
Added method EventHandler::Connection::post_init
--------------------------------------------------
Version: 0.2.0, released 10Apr06
Added method EventHandler::stop
@@ -0,0 +1,4 @@
This note details the usage of EventMachine's built-in support for SMTP. EM
supports both client and server connections, which will be described in
separate sections.
@@ -0,0 +1,148 @@
EventMachine (EM) adds two different formalisms for lightweight concurrency
to the Ruby programmer's toolbox: spawned processes and deferrables. This
note will show you how to use spawned processes. For more information, see
the separate document LIGHTWEIGHT_CONCURRENCY.
=== What are Spawned Processes?
Spawned Processes in EventMachine are inspired directly by the "processes"
found in the Erlang programming language. EM deliberately borrows much (but
not all) of Erlang's terminology. However, EM's spawned processes differ from
Erlang's in ways that reflect not only Ruby style, but also the fact that
Ruby is not a functional language like Erlang.
Let's proceed with a complete, working code sample that we will analyze line
by line. Here's an EM implementation of the "ping-pong" program that also
appears in the Erlang tutorial:
require 'eventmachine'
EM.run {
pong = EM.spawn {|x, ping|
puts "Pong received #{x}"
ping.notify( x-1 )
}
ping = EM.spawn {|x|
if x > 0
puts "Pinging #{x}"
pong.notify x, self
else
EM.stop
end
}
ping.notify 3
}
If you run this program, you'll see the following output:
Pinging 3
Pong received 3
Pinging 2
Pong received 2
Pinging 1
Pong received 1
Let's take it step by step.
EventMachine#spawn works very much like the built-in function spawn in
Erlang. It returns a reference to a Ruby object of class
EventMachine::SpawnedProcess, which is actually a schedulable entity. In
Erlang, the value returned from spawn is called a "process identifier" or
"pid." But we'll refer to the Ruby object returned from EM#spawn simply as a
"spawned process."
You pass a Ruby block with zero or more parameters to EventMachine#spawn.
Like all Ruby blocks, this one is a closure, so it can refer to variables
defined in the local context when you call EM#spawn.
However, the code block passed to EM#spawn does NOT execute immediately by
default. Rather, it will execute only when the Spawned Object is "notified."
In Erlang, this process is called "message passing," and is done with the
operator !, but in Ruby it's done simply by calling the #notify method of a
spawned-process object. The parameters you pass to #notify must match those
defined in the block that was originally passed to EM#spawn.
When you call the #notify method of a spawned-process object, EM's reactor
core will execute the code block originally passed to EM#spawn, at some point
in the future. (#notify itself merely adds a notification to the object's
message queue and ALWAYS returns immediately.)
When a SpawnedProcess object executes a notification, it does so in the
context of the SpawnedProcess object itself. The notified code block can see
local context from the point at which EM#spawn was called. However, the value
of "self" inside the notified code block is a reference to the SpawnedProcesss
object itself.
An EM spawned process is nothing more than a Ruby object with a message
queue attached to it. You can have any number of spawned processes in your
program without compromising scalability. You can notify a spawned process
any number of times, and each notification will cause a "message" to be
placed in the queue of the spawned process. Spawned processes with non-empty
message queues are scheduled for execution automatically by the EM reactor.
Spawned processes with no visible references are garbage-collected like any
other Ruby object.
Back to our code sample:
pong = EM.spawn {|x, ping|
puts "Pong received #{x}"
ping.notify( x-1 )
}
This simply creates a spawned process and assigns it to the local variable
pong. You can see that the spawned code block takes a numeric parameter and a
reference to another spawned process. When pong is notified, it expects to
receive arguments corresponding to these two parameters. It simply prints out
the number it receives as the first argument. Then it notifies the spawned
process referenced by the second argument, passing it the first argument
minus 1.
And then the block ends, which is crucial because otherwise nothing else
can run. (Remember that in LC, scheduled entities run to completion and are
never preempted.)
On to the next bit of the code sample:
ping = EM.spawn {|x|
if x > 0
puts "Pinging #{x}"
pong.notify x, self
else
EM.stop
end
}
Here, we're spawning a process that takes a single (numeric) parameter. If
the parameter is greater than zero, the block writes it to the console. It
then notifies the spawned process referenced by the pong local variable,
passing as arguments its number argument, and a reference to itself. The
latter reference, as you saw above, is used by pong to send a return
notification.
If the ping process receives a zero value, it will stop the reactor loop and
end the program.
Now we've created a pair of spawned processes, but nothing else has happened.
If we stop now, the program will spin in the EM reactor loop, doing nothing
at all. Our spawned processes will never be scheduled for execution.
But look at the next line in the code sample:
ping.notify 3
This line gets the ping-pong ball rolling. We call ping's #notify method,
passing the argument 3. This causes a message to be sent to the ping spawned
process. The message contains the single argument, and it causes the EM
reactor to schedule the ping process. And this in turn results in the
execution of the Ruby code block passed to EM#spawn when ping was created.
Everything else proceeds as a result of the messages that are subsequently
passed to each other by the spawned processes.
[TODO, present the outbound network i/o use case, and clarify that spawned
processes are interleaved with normal i/o operations and don't interfere
with them at all. Also, blame Erlang for the confusing term "process"]
@@ -0,0 +1,8 @@
TODO List:
12Aug06: Noticed by Don Stocks. A TCP connect-request that results
in a failed DNS resolution fires a fatal error back to user code.
Uuuuuugly. We should probably cause an unbind event to get fired
instead, and add some parameterization so the caller can detect
the nature of the failure.
@@ -0,0 +1,18 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class EchoServer < EM::Connection
def receive_data(data)
send_data(data)
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, EchoServer)
end
@@ -0,0 +1,22 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class EchoServer < EM::Connection
def receive_data(data)
if data.strip =~ /exit$/i
EventMachine.stop
else
send_data(data)
end
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, EchoServer)
end
@@ -0,0 +1,149 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
@@connected_clients = Array.new
DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s*(.+)/.freeze
attr_reader :username
#
# EventMachine handlers
#
def post_init
@username = nil
puts "A client has connected..."
ask_username
end
def unbind
@@connected_clients.delete(self)
puts "[info] #{@username} has left" if entered_username?
end
def receive_data(data)
if entered_username?
handle_chat_message(data.strip)
else
handle_username(data.strip)
end
end
#
# Username handling
#
def entered_username?
!@username.nil? && !@username.empty?
end # entered_username?
def handle_username(input)
if input.empty?
send_line("Blank usernames are not allowed. Try again.")
ask_username
else
@username = input
@@connected_clients.push(self)
self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
puts "#{@username} has joined"
self.send_line("[info] Ohai, #{@username}")
end
end # handle_username(input)
def ask_username
self.send_line("[info] Enter your username:")
end # ask_username
#
# Message handling
#
def handle_chat_message(msg)
if command?(msg)
self.handle_command(msg)
else
if direct_message?(msg)
self.handle_direct_message(msg)
else
self.announce(msg, "#{@username}:")
end
end
end # handle_chat_message(msg)
def direct_message?(input)
input =~ DM_REGEXP
end # direct_message?(input)
def handle_direct_message(input)
username, message = parse_direct_message(input)
if connection = @@connected_clients.find { |c| c.username == username }
puts "[dm] @#{@username} => @#{username}"
connection.send_line("[dm] @#{@username}: #{message}")
else
send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
end
end # handle_direct_message(input)
def parse_direct_message(input)
return [$1, $2] if input =~ DM_REGEXP
end # parse_direct_message(input)
#
# Commands handling
#
def command?(input)
input =~ /(exit|status)$/i
end # command?(input)
def handle_command(cmd)
case cmd
when /exit$/i then self.close_connection
when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
end
end # handle_command(cmd)
#
# Helpers
#
def announce(msg = nil, prefix = "[chat server]")
@@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
end # announce(msg)
def number_of_connected_clients
@@connected_clients.size
end # number_of_connected_clients
def other_peers
@@connected_clients.reject { |c| self == c }
end # other_peers
def send_line(line)
self.send_data("#{line}\n")
end # send_line(line)
def usernames
@@connected_clients.map { |c| c.username }
end # usernames
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
#
# EventMachine handlers
#
def post_init
puts "A client has connected..."
end
def unbind
puts "A client has left..."
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,43 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
@@connected_clients = Array.new
#
# EventMachine handlers
#
def post_init
@@connected_clients.push(self)
puts "A client has connected..."
end
def unbind
@@connected_clients.delete(self)
puts "A client has left..."
end
#
# Helpers
#
def other_peers
@@connected_clients.reject { |c| self == c }
end # other_peers
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,98 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
@@connected_clients = Array.new
attr_reader :username
#
# EventMachine handlers
#
def post_init
@username = nil
puts "A client has connected..."
ask_username
end
def unbind
@@connected_clients.delete(self)
puts "A client has left..."
end
def receive_data(data)
if entered_username?
handle_chat_message(data.strip)
else
handle_username(data.strip)
end
end
#
# Username handling
#
def entered_username?
!@username.nil? && !@username.empty?
end # entered_username?
def handle_username(input)
if input.empty?
send_line("Blank usernames are not allowed. Try again.")
ask_username
else
@username = input
@@connected_clients.push(self)
self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
puts "#{@username} has joined"
self.send_line("[info] Ohai, #{@username}")
end
end # handle_username(input)
def ask_username
self.send_line("[info] Enter your username:")
end # ask_username
#
# Message handling
#
def handle_chat_message(msg)
raise NotImplementedError
end
#
# Helpers
#
def other_peers
@@connected_clients.reject { |c| self == c }
end # other_peers
def send_line(line)
self.send_data("#{line}\n")
end # send_line(line)
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,121 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
@@connected_clients = Array.new
attr_reader :username
#
# EventMachine handlers
#
def post_init
@username = nil
puts "A client has connected..."
ask_username
end
def unbind
@@connected_clients.delete(self)
puts "[info] #{@username} has left" if entered_username?
end
def receive_data(data)
if entered_username?
handle_chat_message(data.strip)
else
handle_username(data.strip)
end
end
#
# Username handling
#
def entered_username?
!@username.nil? && !@username.empty?
end # entered_username?
def handle_username(input)
if input.empty?
send_line("Blank usernames are not allowed. Try again.")
ask_username
else
@username = input
@@connected_clients.push(self)
self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
puts "#{@username} has joined"
self.send_line("[info] Ohai, #{@username}")
end
end # handle_username(input)
def ask_username
self.send_line("[info] Enter your username:")
end # ask_username
#
# Message handling
#
def handle_chat_message(msg)
if command?(msg)
self.handle_command(msg)
else
self.announce(msg, "#{@username}:")
end
end
#
# Commands handling
#
def command?(input)
input =~ /exit$/i
end # command?(input)
def handle_command(cmd)
case cmd
when /exit$/i then self.close_connection
end
end # handle_command(cmd)
#
# Helpers
#
def announce(msg = nil, prefix = "[chat server]")
@@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
end # announce(msg)
def other_peers
@@connected_clients.reject { |c| self == c }
end # other_peers
def send_line(line)
self.send_data("#{line}\n")
end # send_line(line)
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,141 @@
#!/usr/bin/env ruby
require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class SimpleChatServer < EM::Connection
@@connected_clients = Array.new
DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s+(.+)/.freeze
attr_reader :username
#
# EventMachine handlers
#
def post_init
@username = nil
puts "A client has connected..."
ask_username
end
def unbind
@@connected_clients.delete(self)
puts "[info] #{@username} has left" if entered_username?
end
def receive_data(data)
if entered_username?
handle_chat_message(data.strip)
else
handle_username(data.strip)
end
end
#
# Username handling
#
def entered_username?
!@username.nil? && !@username.empty?
end # entered_username?
def handle_username(input)
if input.empty?
send_line("Blank usernames are not allowed. Try again.")
ask_username
else
@username = input
@@connected_clients.push(self)
self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
puts "#{@username} has joined"
self.send_line("[info] Ohai, #{@username}")
end
end # handle_username(input)
def ask_username
self.send_line("[info] Enter your username:")
end # ask_username
#
# Message handling
#
def handle_chat_message(msg)
if command?(msg)
self.handle_command(msg)
else
if direct_message?(msg)
self.handle_direct_message(msg)
else
self.announce(msg, "#{@username}:")
end
end
end # handle_chat_message(msg)
def direct_message?(input)
input =~ DM_REGEXP
end # direct_message?(input)
def handle_direct_message(input)
username, message = parse_direct_message(input)
if connection = @@connected_clients.find { |c| c.username == username }
puts "[dm] @#{@username} => @#{username}"
connection.send_line("[dm] @#{@username}: #{message}")
else
send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
end
end # handle_direct_message(input)
def parse_direct_message(input)
return [$1, $2] if input =~ DM_REGEXP
end # parse_direct_message(input)
#
# Commands handling
#
def command?(input)
input =~ /(exit|status)$/i
end # command?(input)
def handle_command(cmd)
case cmd
when /exit$/i then self.close_connection
when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
end
end # handle_command(cmd)
#
# Helpers
#
def announce(msg = nil, prefix = "[chat server]")
@@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
end # announce(msg)
def other_peers
@@connected_clients.reject { |c| self == c }
end # other_peers
def send_line(line)
self.send_data("#{line}\n")
end # send_line(line)
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }
EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
end
@@ -0,0 +1,43 @@
require File.dirname(__FILE__) + '/helper'
EM.run do
# Create a channel to push data to, this could be stocks...
RandChannel = EM::Channel.new
# The server simply subscribes client connections to the channel on connect,
# and unsubscribes them on disconnect.
class Server < EM::Connection
def self.start(host = '127.0.0.1', port = 8000)
EM.start_server(host, port, self)
end
def post_init
@sid = RandChannel.subscribe { |m| send_data "#{m.inspect}\n" }
end
def unbind
RandChannel.unsubscribe @sid
end
end
Server.start
# Two client connections, that just print what they receive.
2.times do
EM.connect('127.0.0.1', 8000) do |c|
c.extend EM::P::LineText2
def c.receive_line(line)
puts "Subscriber: #{signature} got #{line}"
end
EM.add_timer(2) { c.close_connection }
end
end
# This part of the example is more fake, but imagine sleep was in fact a
# long running calculation to achieve the value.
40.times do
EM.defer lambda { v = sleep(rand * 2); RandChannel << [Time.now, v] }
end
EM.add_timer(5) { EM.stop }
end
@@ -0,0 +1,2 @@
require File.dirname(__FILE__) + '/helper'
@@ -0,0 +1,15 @@
require File.dirname(__FILE__) + '/helper'
EM.run do
array = (1..100).to_a
tickloop = EM.tick_loop do
if array.empty?
:stop
else
puts array.shift
end
end
tickloop.on_stop { EM.stop }
end
@@ -0,0 +1,32 @@
require File.dirname(__FILE__) + '/helper'
class TickCounter
attr_reader :start_time, :count
def initialize
reset
@tick_loop = EM.tick_loop(method(:tick))
end
def reset
@count = 0
@start_time = EM.current_time
end
def tick
@count += 1
end
def rate
@count / (EM.current_time - @start_time)
end
end
period = 5
EM.run do
counter = TickCounter.new
EM.add_periodic_timer(period) do
puts "Ticks per second: #{counter.rate} (mean of last #{period}s)"
counter.reset
end
end
@@ -0,0 +1,2 @@
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'eventmachine'
+269
View File
@@ -0,0 +1,269 @@
SHELL = /bin/sh
# V=0 quiet, V=1 verbose. other values don't work.
V = 0
V0 = $(V:0=)
Q1 = $(V:1=)
Q = $(Q1:0=@)
ECHO1 = $(V:1=@ :)
ECHO = $(ECHO1:0=@ echo)
NULLCMD = :
#### Start of system configuration section. ####
srcdir = .
topdir = /usr/include/ruby-3.2.0
hdrdir = $(topdir)
arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0
PATH_SEPARATOR = :
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
prefix = $(DESTDIR)/usr
rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME)
rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME)
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
exec_prefix = $(prefix)
vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby
sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby
rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME)
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
sitehdrdir = $(rubyhdrdir)/site_ruby
rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version)
vendorlibdir = $(vendordir)/$(ruby_version)
vendordir = $(rubylibprefix)/vendor_ruby
sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby
sitelibdir = $(sitedir)/$(ruby_version)
sitedir = $(DESTDIR)/usr/local/lib/site_ruby
rubyarchdir = $(rubyarchprefix)/$(ruby_version)
rubylibdir = $(rubylibprefix)/$(ruby_version)
sitearchincludedir = $(includedir)/$(sitearch)
archincludedir = $(includedir)/$(arch)
sitearchlibdir = $(libdir)/$(sitearch)
archlibdir = $(libdir)/$(arch)
ridir = $(datarootdir)/$(RI_BASE_NAME)
mandir = $(datarootdir)/man
localedir = $(datarootdir)/locale
libdir = $(exec_prefix)/lib
psdir = $(docdir)
pdfdir = $(docdir)
dvidir = $(docdir)
htmldir = $(docdir)
infodir = $(datarootdir)/info
docdir = $(datarootdir)/doc/$(PACKAGE)
oldincludedir = $(DESTDIR)/usr/include
includedir = $(prefix)/include
runstatedir = $(DESTDIR)/var/run
localstatedir = $(DESTDIR)/var
sharedstatedir = $(prefix)/com
sysconfdir = $(DESTDIR)/etc
datadir = $(datarootdir)
datarootdir = $(prefix)/share
libexecdir = $(exec_prefix)/libexec
sbindir = $(exec_prefix)/sbin
bindir = $(exec_prefix)/bin
archdir = $(rubyarchdir)
CC_WRAPPER =
CC = x86_64-linux-gnu-gcc
CXX = x86_64-linux-gnu-g++
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS)
empty =
OUTFLAG = -o $(empty)
COUTFLAG = -o $(empty)
CSRCFLAG = $(empty)
RUBY_EXTCONF_H =
cflags = $(optflags) $(debugflags) $(warnflags)
cxxflags =
optflags = -O3 -fno-fast-math
debugflags = -ggdb3
warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef
cppflags =
CCDLFLAGS = -fPIC
CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 -fPIC $(ARCH_FLAG)
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
DEFS =
CPPFLAGS = -DHAVE_OPENSSL_SSL_H -DHAVE_OPENSSL_ERR_H -DWITH_SSL -DBUILD_FOR_RUBY -DHAVE_RB_THREAD_CALL_WITHOUT_GVL -DHAVE_RB_THREAD_FD_SELECT -DHAVE_TYPE_RB_FDSET_T -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_RB_TIME_NEW -DHAVE_INOTIFY_INIT -DHAVE_INOTIFY -DHAVE_WRITEV -DHAVE_PIPE2 -DHAVE_ACCEPT4 -DHAVE_CONST_SOCK_CLOEXEC -DOS_UNIX -DHAVE_EPOLL_CREATE -DHAVE_EPOLL -DHAVE_CLOCK_GETTIME -DHAVE_CONST_CLOCK_MONOTONIC_RAW -DHAVE_CONST_CLOCK_MONOTONIC -DHAVE_MAKE_PAIR -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags)
CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 $(ARCH_FLAG)
ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed
dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now
ARCH_FLAG =
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
LDSHARED = $(CXX) -shared
LDSHAREDXX = $(CXX) -shared
AR = x86_64-linux-gnu-gcc-ar
EXEEXT =
RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2
RUBY_SO_NAME = ruby-3.2
RUBYW_INSTALL_NAME =
RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
RUBYW_BASE_NAME = rubyw
RUBY_BASE_NAME = ruby
arch = x86_64-linux-gnu
sitearch = $(arch)
ruby_version = 3.2.0
ruby = $(bindir)/$(RUBY_BASE_NAME)3.2
RUBY = $(ruby)
BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2
ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
RM = rm -f
RM_RF = rm -fr
RMDIRS = rmdir --ignore-fail-on-non-empty -p
MAKEDIRS = /bin/mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
COPY = cp
TOUCH = exit >
#### End of system configuration section. ####
preload =
libpath = . $(archlibdir)
LIBPATH = -L. -L$(archlibdir)
DEFFILE =
CLEANFILES = mkmf.log
DISTCLEANFILES =
DISTCLEANDIRS =
extout =
extout_prefix =
target_prefix =
LOCAL_LIBS =
LIBS = $(LIBRUBYARG_SHARED) -lssl -lcrypto -lcrypto -lssl -lm -lpthread -lc
ORIG_SRCS = binder.cpp cmain.cpp ed.cpp em.cpp kb.cpp page.cpp pipe.cpp rubymain.cpp ssl.cpp
SRCS = $(ORIG_SRCS)
OBJS = binder.o cmain.o ed.o em.o kb.o page.o pipe.o rubymain.o ssl.o
HDRS = $(srcdir)/binder.h $(srcdir)/ed.h $(srcdir)/em.h $(srcdir)/eventmachine.h $(srcdir)/page.h $(srcdir)/project.h $(srcdir)/ssl.h
LOCAL_HDRS =
TARGET = rubyeventmachine
TARGET_NAME = rubyeventmachine
TARGET_ENTRY = Init_$(TARGET_NAME)
DLLIB = $(TARGET).so
EXTSTATIC =
STATIC_LIB =
TIMESTAMP_DIR = .
BINDIR = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
HDRDIR = $(sitehdrdir)$(target_prefix)
ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
TARGET_SO_DIR =
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
CLEANLIBS = $(TARGET_SO) false
CLEANOBJS = $(OBJS) *.bak
TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time
all: $(DLLIB)
static: $(STATIC_LIB)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
clean-static::
clean-rb-default::
clean-rb::
clean-so::
clean: clean-so clean-static clean-rb-default clean-rb
-$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
distclean-rb-default::
distclean-rb::
distclean-so::
distclean-static::
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
-$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
realclean: distclean
install: install-so install-rb
install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
clean-static::
-$(Q)$(RM) $(STATIC_LIB)
install-rb: pre-install-rb do-install-rb install-rb-default
install-rb-default: pre-install-rb-default do-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
do-install-rb:
do-install-rb-default:
pre-install-rb-default:
@$(NULLCMD)
$(TARGET_SO_DIR_TIMESTAMP):
$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
$(Q) $(TOUCH) $@
site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb
.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
.cc.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cc.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.mm.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.mm.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cxx.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cxx.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cpp.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cpp.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.c.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.c.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.m.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.m.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
$(TARGET_SO): $(OBJS) Makefile
$(ECHO) linking shared-object $(DLLIB)
-$(Q)$(RM) $(@)
$(Q) $(LDSHAREDXX) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
$(OBJS): $(HDRS) $(ruby_headers)
+124
View File
@@ -0,0 +1,124 @@
/*****************************************************************************
$Id$
File: binder.cpp
Date: 07Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include "project.h"
#define DEV_URANDOM "/dev/urandom"
std::map<uintptr_t, Bindable_t*> Bindable_t::BindingBag;
/********************************
STATIC Bindable_t::CreateBinding
********************************/
uintptr_t Bindable_t::CreateBinding()
{
static uintptr_t num = 0;
while(BindingBag[++num]) {}
return num;
}
#if 0
string Bindable_t::CreateBinding()
{
static int index = 0;
static string seed;
if ((index >= 1000000) || (seed.length() == 0)) {
#ifdef OS_UNIX
int fd = open (DEV_URANDOM, O_RDONLY);
if (fd < 0)
throw std::runtime_error ("No entropy device");
unsigned char u[16];
size_t r = read (fd, u, sizeof(u));
if (r < sizeof(u))
throw std::runtime_error ("Unable to read entropy device");
unsigned char *u1 = (unsigned char*)u;
char u2 [sizeof(u) * 2 + 1];
for (size_t i=0; i < sizeof(u); i++)
sprintf (u2 + (i * 2), "%02x", u1[i]);
seed = string (u2);
#endif
#ifdef OS_WIN32
UUID uuid;
UuidCreate (&uuid);
unsigned char *uuidstring = NULL;
UuidToString (&uuid, &uuidstring);
if (!uuidstring)
throw std::runtime_error ("Unable to read uuid");
seed = string ((const char*)uuidstring);
RpcStringFree (&uuidstring);
#endif
index = 0;
}
stringstream ss;
ss << seed << (++index);
return ss.str();
}
#endif
/*****************************
STATIC: Bindable_t::GetObject
*****************************/
Bindable_t *Bindable_t::GetObject (const uintptr_t binding)
{
std::map<uintptr_t, Bindable_t*>::const_iterator i = BindingBag.find (binding);
if (i != BindingBag.end())
return i->second;
else
return NULL;
}
/**********************
Bindable_t::Bindable_t
**********************/
Bindable_t::Bindable_t()
{
Binding = Bindable_t::CreateBinding();
BindingBag [Binding] = this;
}
/***********************
Bindable_t::~Bindable_t
***********************/
Bindable_t::~Bindable_t() NO_EXCEPT_FALSE
{
BindingBag.erase (Binding);
}
+52
View File
@@ -0,0 +1,52 @@
/*****************************************************************************
$Id$
File: binder.h
Date: 07Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __ObjectBindings__H_
#define __ObjectBindings__H_
#if __cplusplus >= 201103L
#define NO_EXCEPT_FALSE noexcept(false)
#else
#define NO_EXCEPT_FALSE
#endif
class Bindable_t
{
public:
static uintptr_t CreateBinding();
static Bindable_t *GetObject (const uintptr_t);
static std::map<uintptr_t, Bindable_t*> BindingBag;
public:
Bindable_t();
virtual ~Bindable_t() NO_EXCEPT_FALSE;
const uintptr_t GetBinding() {return Binding;}
private:
uintptr_t Binding;
};
#endif // __ObjectBindings__H_
+988
View File
@@ -0,0 +1,988 @@
/*****************************************************************************
$Id$
File: cmain.cpp
Date: 06Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include "project.h"
/* 21Sep09: ruby 1.9 defines macros for common i/o functions that point to rb_w32_* implementations.
We need to undef the stat to fix a build failure in evma_send_file_data_to_connection.
See http://groups.google.com/group/eventmachine/browse_thread/thread/fc60d9bb738ffc71
*/
#if defined(BUILD_FOR_RUBY) && defined(OS_WIN32)
#undef stat
#undef fstat
#endif
static EventMachine_t *EventMachine;
static Poller_t Poller = Poller_Default;
extern "C" void ensure_eventmachine (const char *caller = "unknown caller")
{
if (!EventMachine) {
const int err_size = 128;
char err_string[err_size];
snprintf (err_string, err_size, "eventmachine not initialized: %s", caller);
#ifdef BUILD_FOR_RUBY
rb_raise(rb_eRuntimeError, "%s", err_string);
#else
throw std::runtime_error (err_string);
#endif
}
}
/***********************
evma_initialize_library
***********************/
extern "C" void evma_initialize_library (EMCallback cb)
{
if (EventMachine)
#ifdef BUILD_FOR_RUBY
rb_raise(rb_eRuntimeError, "eventmachine already initialized: evma_initialize_library");
#else
throw std::runtime_error ("eventmachine already initialized: evma_initialize_library");
#endif
EventMachine = new EventMachine_t (cb, Poller);
}
/********************
evma_release_library
********************/
extern "C" void evma_release_library()
{
ensure_eventmachine("evma_release_library");
delete EventMachine;
EventMachine = NULL;
}
/*********************
evma_run_machine_once
*********************/
extern "C" bool evma_run_machine_once()
{
ensure_eventmachine("evma_run_machine_once");
return EventMachine->RunOnce();
}
/****************
evma_run_machine
****************/
extern "C" void evma_run_machine()
{
ensure_eventmachine("evma_run_machine");
EventMachine->Run();
}
/**************************
evma_install_oneshot_timer
**************************/
extern "C" const uintptr_t evma_install_oneshot_timer (uint64_t milliseconds)
{
ensure_eventmachine("evma_install_oneshot_timer");
return EventMachine->InstallOneshotTimer (milliseconds);
}
/**********************
evma_connect_to_server
**********************/
extern "C" const uintptr_t evma_connect_to_server (const char *bind_addr, int bind_port, const char *server, int port)
{
ensure_eventmachine("evma_connect_to_server");
return EventMachine->ConnectToServer (bind_addr, bind_port, server, port);
}
/***************************
evma_connect_to_unix_server
***************************/
extern "C" const uintptr_t evma_connect_to_unix_server (const char *server)
{
ensure_eventmachine("evma_connect_to_unix_server");
return EventMachine->ConnectToUnixServer (server);
}
/**************
evma_attach_fd
**************/
extern "C" const uintptr_t evma_attach_fd (int file_descriptor, int watch_mode)
{
ensure_eventmachine("evma_attach_fd");
return EventMachine->AttachFD (file_descriptor, watch_mode ? true : false);
}
/**************
evma_detach_fd
**************/
extern "C" int evma_detach_fd (const uintptr_t binding)
{
ensure_eventmachine("evma_detach_fd");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return EventMachine->DetachFD (ed);
else
#ifdef BUILD_FOR_RUBY
rb_raise(rb_eRuntimeError, "invalid binding to detach");
#else
throw std::runtime_error ("invalid binding to detach");
#endif
return -1;
}
/************************
evma_get_file_descriptor
************************/
extern "C" int evma_get_file_descriptor (const uintptr_t binding)
{
ensure_eventmachine("evma_get_file_descriptor");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetSocket();
else
#ifdef BUILD_FOR_RUBY
rb_raise(rb_eRuntimeError, "invalid binding to get_fd");
#else
throw std::runtime_error ("invalid binding to get_fd");
#endif
return -1;
}
/***********************
evma_is_notify_readable
***********************/
extern "C" int evma_is_notify_readable (const uintptr_t binding)
{
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
return cd->IsNotifyReadable() ? 1 : 0;
return -1;
}
/************************
evma_set_notify_readable
************************/
extern "C" void evma_set_notify_readable (const uintptr_t binding, int mode)
{
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
cd->SetNotifyReadable (mode ? true : false);
}
/***********************
evma_is_notify_writable
***********************/
extern "C" int evma_is_notify_writable (const uintptr_t binding)
{
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
return cd->IsNotifyWritable() ? 1 : 0;
return -1;
}
/************************
evma_set_notify_writable
************************/
extern "C" void evma_set_notify_writable (const uintptr_t binding, int mode)
{
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
cd->SetNotifyWritable (mode ? true : false);
}
/**********
evma_pause
**********/
extern "C" int evma_pause (const uintptr_t binding)
{
EventableDescriptor *cd = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
return cd->Pause() ? 1 : 0;
return 0;
}
/***********
evma_resume
***********/
extern "C" int evma_resume (const uintptr_t binding)
{
EventableDescriptor *cd = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
return cd->Resume() ? 1 : 0;
return 0;
}
/**************
evma_is_paused
**************/
extern "C" int evma_is_paused (const uintptr_t binding)
{
EventableDescriptor *cd = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
return cd->IsPaused() ? 1 : 0;
return 0;
}
/************************
evma_num_close_scheduled
************************/
extern "C" int evma_num_close_scheduled ()
{
ensure_eventmachine("evma_num_close_scheduled");
return EventMachine->NumCloseScheduled;
}
/**********************
evma_create_tcp_server
**********************/
extern "C" const uintptr_t evma_create_tcp_server (const char *address, int port)
{
ensure_eventmachine("evma_create_tcp_server");
return EventMachine->CreateTcpServer (address, port);
}
/******************************
evma_create_unix_domain_server
******************************/
extern "C" const uintptr_t evma_create_unix_domain_server (const char *filename)
{
ensure_eventmachine("evma_create_unix_domain_server");
return EventMachine->CreateUnixDomainServer (filename);
}
/***********************
evma_attach_sd
************************/
extern "C" const uintptr_t evma_attach_sd (int sd)
{
ensure_eventmachine("evma_attach_sd");
return EventMachine->AttachSD (sd);
}
/*************************
evma_open_datagram_socket
*************************/
extern "C" const uintptr_t evma_open_datagram_socket (const char *address, int port)
{
ensure_eventmachine("evma_open_datagram_socket");
return EventMachine->OpenDatagramSocket (address, port);
}
/******************
evma_open_keyboard
******************/
extern "C" const uintptr_t evma_open_keyboard()
{
ensure_eventmachine("evma_open_keyboard");
return EventMachine->OpenKeyboard();
}
/*******************
evma_watch_filename
*******************/
extern "C" const uintptr_t evma_watch_filename (const char *fname)
{
ensure_eventmachine("evma_watch_filename");
return EventMachine->WatchFile(fname);
}
/*********************
evma_unwatch_filename
*********************/
extern "C" void evma_unwatch_filename (const uintptr_t sig)
{
ensure_eventmachine("evma_unwatch_file");
EventMachine->UnwatchFile(sig);
}
/**************
evma_watch_pid
**************/
extern "C" const uintptr_t evma_watch_pid (int pid)
{
ensure_eventmachine("evma_watch_pid");
return EventMachine->WatchPid(pid);
}
/****************
evma_unwatch_pid
****************/
extern "C" void evma_unwatch_pid (const uintptr_t sig)
{
ensure_eventmachine("evma_unwatch_pid");
EventMachine->UnwatchPid(sig);
}
/****************************
evma_send_data_to_connection
****************************/
extern "C" int evma_send_data_to_connection (const uintptr_t binding, const char *data, int data_length)
{
ensure_eventmachine("evma_send_data_to_connection");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->SendOutboundData(data, data_length);
return -1;
}
/******************
evma_send_datagram
******************/
extern "C" int evma_send_datagram (const uintptr_t binding, const char *data, int data_length, const char *address, int port)
{
ensure_eventmachine("evma_send_datagram");
DatagramDescriptor *dd = dynamic_cast <DatagramDescriptor*> (Bindable_t::GetObject (binding));
if (dd)
return dd->SendOutboundDatagram(data, data_length, address, port);
return -1;
}
/*********************
evma_close_connection
*********************/
extern "C" void evma_close_connection (const uintptr_t binding, int after_writing)
{
ensure_eventmachine("evma_close_connection");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
ed->ScheduleClose (after_writing ? true : false);
}
/***********************************
evma_report_connection_error_status
***********************************/
extern "C" int evma_report_connection_error_status (const uintptr_t binding)
{
ensure_eventmachine("evma_report_connection_error_status");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->ReportErrorStatus();
return -1;
}
/********************
evma_stop_tcp_server
********************/
extern "C" void evma_stop_tcp_server (const uintptr_t binding)
{
ensure_eventmachine("evma_stop_tcp_server");
AcceptorDescriptor::StopAcceptor (binding);
}
/*****************
evma_stop_machine
*****************/
extern "C" void evma_stop_machine()
{
ensure_eventmachine("evma_stop_machine");
EventMachine->ScheduleHalt();
}
/*****************
evma_stopping
*****************/
extern "C" bool evma_stopping()
{
ensure_eventmachine("evma_stopping");
return EventMachine->Stopping();
}
/**************
evma_start_tls
**************/
extern "C" void evma_start_tls (const uintptr_t binding)
{
ensure_eventmachine("evma_start_tls");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
ed->StartTls();
}
/******************
evma_set_tls_parms
******************/
extern "C" void evma_set_tls_parms (const uintptr_t binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer, int fail_if_no_peer_cert, const char *sni_hostname, const char *cipherlist, const char *ecdh_curve, const char *dhparam, int ssl_version)
{
ensure_eventmachine("evma_set_tls_parms");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
ed->SetTlsParms (privatekey_filename, certchain_filename, (verify_peer == 1 ? true : false), (fail_if_no_peer_cert == 1 ? true : false), sni_hostname, cipherlist, ecdh_curve, dhparam, ssl_version);
}
/******************
evma_get_peer_cert
******************/
#ifdef WITH_SSL
extern "C" X509 *evma_get_peer_cert (const uintptr_t binding)
{
ensure_eventmachine("evma_get_peer_cert");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetPeerCert();
return NULL;
}
#endif
/******************
evma_get_cipher_bits
******************/
#ifdef WITH_SSL
extern "C" int evma_get_cipher_bits (const uintptr_t binding)
{
ensure_eventmachine("evma_get_cipher_bits");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetCipherBits();
return -1;
}
#endif
/******************
evma_get_cipher_name
******************/
#ifdef WITH_SSL
extern "C" const char *evma_get_cipher_name (const uintptr_t binding)
{
ensure_eventmachine("evma_get_cipher_name");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetCipherName();
return NULL;
}
#endif
/******************
evma_get_cipher_protocol
******************/
#ifdef WITH_SSL
extern "C" const char *evma_get_cipher_protocol (const uintptr_t binding)
{
ensure_eventmachine("evma_get_cipher_protocol");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetCipherProtocol();
return NULL;
}
#endif
/******************
evma_get_sni_hostname
******************/
#ifdef WITH_SSL
extern "C" const char *evma_get_sni_hostname (const uintptr_t binding)
{
ensure_eventmachine("evma_get_sni_hostname");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
return ed->GetSNIHostname();
return NULL;
}
#endif
/********************
evma_accept_ssl_peer
********************/
#ifdef WITH_SSL
extern "C" void evma_accept_ssl_peer (const uintptr_t binding)
{
ensure_eventmachine("evma_accept_ssl_peer");
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
if (cd)
cd->AcceptSslPeer();
}
#endif
/*****************
evma_get_peername
*****************/
extern "C" int evma_get_peername (const uintptr_t binding, struct sockaddr *sa, socklen_t *len)
{
ensure_eventmachine("evma_get_peername");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ed->GetPeername (sa, len) ? 1 : 0;
}
else
return 0;
}
/*****************
evma_get_sockname
*****************/
extern "C" int evma_get_sockname (const uintptr_t binding, struct sockaddr *sa, socklen_t *len)
{
ensure_eventmachine("evma_get_sockname");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ed->GetSockname (sa, len) ? 1 : 0;
}
else
return 0;
}
/***********************
evma_get_subprocess_pid
***********************/
#ifdef OS_UNIX
extern "C" int evma_get_subprocess_pid (const uintptr_t binding, pid_t *pid)
{
ensure_eventmachine("evma_get_subprocess_pid");
PipeDescriptor *pd = dynamic_cast <PipeDescriptor*> (Bindable_t::GetObject (binding));
if (pd) {
return pd->GetSubprocessPid (pid) ? 1 : 0;
}
else if (pid && EventMachine->SubprocessPid) {
*pid = EventMachine->SubprocessPid;
return 1;
}
else
return 0;
}
#else
extern "C" int evma_get_subprocess_pid (const uintptr_t binding UNUSED, pid_t *pid UNUSED)
{
return 0;
}
#endif
/**************************
evma_get_subprocess_status
**************************/
extern "C" int evma_get_subprocess_status (const uintptr_t binding UNUSED, int *status)
{
ensure_eventmachine("evma_get_subprocess_status");
if (status) {
*status = EventMachine->SubprocessExitStatus;
return 1;
}
else
return 0;
}
/*************************
evma_get_connection_count
*************************/
extern "C" int evma_get_connection_count()
{
ensure_eventmachine("evma_get_connection_count");
return EventMachine->GetConnectionCount();
}
/*********************
evma_signal_loopbreak
*********************/
extern "C" void evma_signal_loopbreak()
{
ensure_eventmachine("evma_signal_loopbreak");
EventMachine->SignalLoopBreaker();
}
/********************************
evma_get_comm_inactivity_timeout
********************************/
extern "C" float evma_get_comm_inactivity_timeout (const uintptr_t binding)
{
ensure_eventmachine("evma_get_comm_inactivity_timeout");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ((float)ed->GetCommInactivityTimeout() / 1000);
}
else
return 0.0; //Perhaps this should be an exception. Access to an unknown binding.
}
/********************************
evma_set_comm_inactivity_timeout
********************************/
extern "C" int evma_set_comm_inactivity_timeout (const uintptr_t binding, float value)
{
ensure_eventmachine("evma_set_comm_inactivity_timeout");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ed->SetCommInactivityTimeout ((uint64_t)(value * 1000));
}
else
return 0; //Perhaps this should be an exception. Access to an unknown binding.
}
/********************************
evma_get_pending_connect_timeout
********************************/
extern "C" float evma_get_pending_connect_timeout (const uintptr_t binding)
{
ensure_eventmachine("evma_get_pending_connect_timeout");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ((float)ed->GetPendingConnectTimeout() / 1000);
}
else
return 0.0;
}
/********************************
evma_set_pending_connect_timeout
********************************/
extern "C" int evma_set_pending_connect_timeout (const uintptr_t binding, float value)
{
ensure_eventmachine("evma_set_pending_connect_timeout");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed) {
return ed->SetPendingConnectTimeout ((uint64_t)(value * 1000));
}
else
return 0;
}
/**********************
evma_set_timer_quantum
**********************/
extern "C" void evma_set_timer_quantum (int interval)
{
ensure_eventmachine("evma_set_timer_quantum");
EventMachine->SetTimerQuantum (interval);
}
/************************
evma_get_max_timer_count
************************/
extern "C" int evma_get_max_timer_count()
{
return EventMachine_t::GetMaxTimerCount();
}
/************************
evma_set_max_timer_count
************************/
extern "C" void evma_set_max_timer_count (int ct)
{
// This may only be called if the reactor is not running.
if (EventMachine)
#ifdef BUILD_FOR_RUBY
rb_raise(rb_eRuntimeError, "eventmachine already initialized: evma_set_max_timer_count");
#else
throw std::runtime_error ("eventmachine already initialized: evma_set_max_timer_count");
#endif
EventMachine_t::SetMaxTimerCount (ct);
}
/******************
evma_get/set_simultaneous_accept_count
******************/
extern "C" void evma_set_simultaneous_accept_count (int count)
{
EventMachine_t::SetSimultaneousAcceptCount(count);
}
extern "C" int evma_get_simultaneous_accept_count()
{
return EventMachine_t::GetSimultaneousAcceptCount();
}
/******************
evma_setuid_string
******************/
extern "C" void evma_setuid_string (const char *username)
{
// We do NOT need to be running an EM instance because this method is static.
EventMachine_t::SetuidString (username);
}
/**********
evma_popen
**********/
extern "C" const uintptr_t evma_popen (char * const*cmd_strings)
{
ensure_eventmachine("evma_popen");
return EventMachine->Socketpair (cmd_strings);
}
/***************************
evma_get_outbound_data_size
***************************/
extern "C" int evma_get_outbound_data_size (const uintptr_t binding)
{
ensure_eventmachine("evma_get_outbound_data_size");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
return ed ? ed->GetOutboundDataSize() : 0;
}
/**************
evma_set_epoll
**************/
extern "C" void evma_set_epoll (int use)
{
if (use)
Poller = Poller_Epoll;
else
Poller = Poller_Default;
}
/***************
evma_set_kqueue
***************/
extern "C" void evma_set_kqueue (int use)
{
if (use)
Poller = Poller_Kqueue;
else
Poller = Poller_Default;
}
/**********************
evma_set_rlimit_nofile
**********************/
extern "C" int evma_set_rlimit_nofile (int nofiles)
{
return EventMachine_t::SetRlimitNofile (nofiles);
}
/*********************************
evma_send_file_data_to_connection
*********************************/
extern "C" int evma_send_file_data_to_connection (const uintptr_t binding, const char *filename)
{
/* This is a sugaring over send_data_to_connection that reads a file into a
* locally-allocated buffer, and sends the file data to the remote peer.
* Return the number of bytes written to the caller.
* TODO, needs to impose a limit on the file size. This is intended only for
* small files. (I don't know, maybe 8K or less.) For larger files, use interleaved
* I/O to avoid slowing the rest of the system down.
* TODO: we should return a code rather than barf, in case of file-not-found.
* TODO, does this compile on Windows?
* TODO, given that we want this to work only with small files, how about allocating
* the buffer on the stack rather than the heap?
*
* Modified 25Jul07. This now returns -1 on file-too-large; 0 for success, and a positive
* errno in case of other errors.
*
* Contributed by Kirk Haines.
*/
char data[32*1024];
int r;
ensure_eventmachine("evma_send_file_data_to_connection");
#if defined(OS_WIN32)
int Fd = open (filename, O_RDONLY|O_BINARY);
#else
int Fd = open (filename, O_RDONLY);
#endif
if (Fd < 0)
return errno;
// From here on, all early returns MUST close Fd.
struct stat st;
if (fstat (Fd, &st)) {
int e = errno;
close (Fd);
return e;
}
off_t filesize = st.st_size;
if (filesize <= 0) {
close (Fd);
return 0;
}
else if (filesize > (off_t) sizeof(data)) {
close (Fd);
return -1;
}
r = read (Fd, data, filesize);
if (r != filesize) {
int e = errno;
close (Fd);
return e;
}
evma_send_data_to_connection (binding, data, r);
close (Fd);
return 0;
}
/****************
evma_start_proxy
*****************/
extern "C" void evma_start_proxy (const uintptr_t from, const uintptr_t to, const unsigned long bufsize, const unsigned long length)
{
ensure_eventmachine("evma_start_proxy");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (from));
if (ed)
ed->StartProxy(to, bufsize, length);
}
/***************
evma_stop_proxy
****************/
extern "C" void evma_stop_proxy (const uintptr_t from)
{
ensure_eventmachine("evma_stop_proxy");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (from));
if (ed)
ed->StopProxy();
}
/******************
evma_proxied_bytes
*******************/
extern "C" unsigned long evma_proxied_bytes (const uintptr_t from)
{
ensure_eventmachine("evma_proxied_bytes");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (from));
if (ed)
return ed->GetProxiedBytes();
else
return 0;
}
/***************************
evma_get_last_activity_time
****************************/
extern "C" uint64_t evma_get_last_activity_time(const uintptr_t from)
{
ensure_eventmachine("evma_get_last_activity_time");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (from));
if (ed)
return ed->GetLastActivity();
else
return 0;
}
/***************************
evma_get_heartbeat_interval
****************************/
extern "C" float evma_get_heartbeat_interval()
{
ensure_eventmachine("evma_get_heartbeat_interval");
return EventMachine->GetHeartbeatInterval();
}
/***************************
evma_set_heartbeat_interval
****************************/
extern "C" int evma_set_heartbeat_interval(float interval)
{
ensure_eventmachine("evma_set_heartbeat_interval");
return EventMachine->SetHeartbeatInterval(interval);
}
/**************************
evma_get_current_loop_time
***************************/
extern "C" uint64_t evma_get_current_loop_time()
{
ensure_eventmachine("evma_get_current_loop_time");
return EventMachine->GetCurrentLoopTime();
}
File diff suppressed because it is too large Load Diff
+457
View File
@@ -0,0 +1,457 @@
/*****************************************************************************
$Id$
File: ed.h
Date: 06Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __EventableDescriptor__H_
#define __EventableDescriptor__H_
class EventMachine_t; // forward reference
#ifdef WITH_SSL
class SslBox_t; // forward reference
#endif
bool SetSocketNonblocking (SOCKET);
bool SetFdCloexec (int);
/*************************
class EventableDescriptor
*************************/
class EventableDescriptor: public Bindable_t
{
public:
EventableDescriptor (SOCKET, EventMachine_t*);
virtual ~EventableDescriptor() NO_EXCEPT_FALSE;
SOCKET GetSocket() {return MySocket;}
void SetSocketInvalid() { MySocket = INVALID_SOCKET; }
void Close();
virtual void Read() = 0;
virtual void Write() = 0;
virtual void Heartbeat() = 0;
// These methods tell us whether the descriptor
// should be selected or polled for read/write.
virtual bool SelectForRead() = 0;
virtual bool SelectForWrite() = 0;
// are we scheduled for a close, or in an error state, or already closed?
bool ShouldDelete();
// Do we have any data to write? This is used by ShouldDelete.
virtual int GetOutboundDataSize() {return 0;}
virtual bool IsWatchOnly(){ return bWatchOnly; }
virtual void ScheduleClose (bool after_writing);
bool IsCloseScheduled();
virtual void HandleError(){ ScheduleClose (false); }
void SetEventCallback (EMCallback);
virtual bool GetPeername (struct sockaddr*, socklen_t*) = 0;
virtual bool GetSockname (struct sockaddr*, socklen_t*) = 0;
virtual bool GetSubprocessPid (pid_t*) {return false;}
virtual void StartTls() {}
virtual void SetTlsParms (const char *, const char *, bool, bool, const char *, const char *, const char *, const char *, int) {}
#ifdef WITH_SSL
virtual X509 *GetPeerCert() {return NULL;}
virtual int GetCipherBits() {return -1;}
virtual const char *GetCipherName() {return NULL;}
virtual const char *GetCipherProtocol() {return NULL;}
virtual const char *GetSNIHostname() {return NULL;}
#endif
virtual uint64_t GetCommInactivityTimeout() {return 0;}
virtual int SetCommInactivityTimeout (uint64_t) {return 0;}
uint64_t GetPendingConnectTimeout();
int SetPendingConnectTimeout (uint64_t value);
uint64_t GetLastActivity() { return LastActivity; }
#ifdef HAVE_EPOLL
struct epoll_event *GetEpollEvent() { return &EpollEvent; }
#endif
#ifdef HAVE_KQUEUE
bool GetKqueueArmWrite() { return bKqueueArmWrite; }
#endif
virtual void StartProxy(const uintptr_t, const unsigned long, const unsigned long);
virtual void StopProxy();
virtual unsigned long GetProxiedBytes(){ return ProxiedBytes; };
virtual void SetProxiedFrom(EventableDescriptor*, const unsigned long);
virtual int SendOutboundData(const char*,unsigned long){ return -1; }
virtual bool IsPaused(){ return bPaused; }
virtual bool Pause(){ bPaused = true; return bPaused; }
virtual bool Resume(){ bPaused = false; return bPaused; }
void SetUnbindReasonCode(int code){ UnbindReasonCode = code; }
virtual int ReportErrorStatus(){ return 0; }
virtual bool IsConnectPending(){ return false; }
virtual uint64_t GetNextHeartbeat();
private:
bool bCloseNow;
bool bCloseAfterWriting;
protected:
SOCKET MySocket;
bool bAttached;
bool bWatchOnly;
EMCallback EventCallback;
void _GenericInboundDispatch (const char *buffer, unsigned long size);
bool _GenericGetPeername (struct sockaddr*, socklen_t*);
bool _GenericGetSockname (struct sockaddr*, socklen_t*);
uint64_t CreatedAt;
bool bCallbackUnbind;
int UnbindReasonCode;
unsigned long BytesToProxy;
EventableDescriptor *ProxyTarget;
EventableDescriptor *ProxiedFrom;
unsigned long ProxiedBytes;
unsigned long MaxOutboundBufSize;
#ifdef HAVE_EPOLL
struct epoll_event EpollEvent;
#endif
#ifdef HAVE_KQUEUE
bool bKqueueArmWrite;
#endif
EventMachine_t *MyEventMachine;
uint64_t PendingConnectTimeout;
uint64_t InactivityTimeout;
uint64_t LastActivity;
uint64_t NextHeartbeat;
bool bPaused;
};
/*************************
class LoopbreakDescriptor
*************************/
class LoopbreakDescriptor: public EventableDescriptor
{
public:
LoopbreakDescriptor (SOCKET, EventMachine_t*);
virtual ~LoopbreakDescriptor() {}
virtual void Read();
virtual void Write();
virtual void Heartbeat() {}
virtual bool SelectForRead() {return true;}
virtual bool SelectForWrite() {return false;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }
};
/**************************
class ConnectionDescriptor
**************************/
class ConnectionDescriptor: public EventableDescriptor
{
public:
ConnectionDescriptor (SOCKET, EventMachine_t*);
virtual ~ConnectionDescriptor();
int SendOutboundData (const char*, unsigned long);
void SetConnectPending (bool f);
virtual void ScheduleClose (bool after_writing);
virtual void HandleError();
void SetNotifyReadable (bool);
void SetNotifyWritable (bool);
void SetAttached (bool);
void SetWatchOnly (bool);
bool Pause();
bool Resume();
bool IsNotifyReadable(){ return bNotifyReadable; }
bool IsNotifyWritable(){ return bNotifyWritable; }
virtual void Read();
virtual void Write();
virtual void Heartbeat();
virtual bool SelectForRead();
virtual bool SelectForWrite();
// Do we have any data to write? This is used by ShouldDelete.
virtual int GetOutboundDataSize() {return OutboundDataSize;}
virtual void StartTls();
virtual void SetTlsParms (const char *, const char *, bool, bool, const char *, const char *, const char *, const char *, int);
#ifdef WITH_SSL
virtual X509 *GetPeerCert();
virtual int GetCipherBits();
virtual const char *GetCipherName();
virtual const char *GetCipherProtocol();
virtual const char *GetSNIHostname();
virtual bool VerifySslPeer(const char*);
virtual void AcceptSslPeer();
#endif
void SetServerMode() {bIsServer = true;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }
virtual uint64_t GetCommInactivityTimeout();
virtual int SetCommInactivityTimeout (uint64_t value);
virtual int ReportErrorStatus();
virtual bool IsConnectPending(){ return bConnectPending; }
protected:
struct OutboundPage {
OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
void Free() {if (Buffer) free (const_cast<char*>(Buffer)); }
const char *Buffer;
int Length;
int Offset;
};
protected:
bool bConnectPending;
bool bNotifyReadable;
bool bNotifyWritable;
bool bReadAttemptedAfterClose;
bool bWriteAttemptedAfterClose;
std::deque<OutboundPage> OutboundPages;
int OutboundDataSize;
#ifdef WITH_SSL
SslBox_t *SslBox;
std::string CertChainFilename;
std::string PrivateKeyFilename;
std::string CipherList;
std::string EcdhCurve;
std::string DhParam;
int Protocols;
bool bHandshakeSignaled;
bool bSslVerifyPeer;
bool bSslFailIfNoPeerCert;
std::string SniHostName;
bool bSslPeerAccepted;
#endif
#ifdef HAVE_KQUEUE
bool bGotExtraKqueueEvent;
#endif
bool bIsServer;
private:
void _UpdateEvents();
void _UpdateEvents(bool, bool);
void _WriteOutboundData();
void _DispatchInboundData (const char *buffer, unsigned long size);
void _DispatchCiphertext();
int _SendRawOutboundData (const char *buffer, unsigned long size);
void _CheckHandshakeStatus();
};
/************************
class DatagramDescriptor
************************/
class DatagramDescriptor: public EventableDescriptor
{
public:
DatagramDescriptor (SOCKET, EventMachine_t*);
virtual ~DatagramDescriptor();
virtual void Read();
virtual void Write();
virtual void Heartbeat();
virtual bool SelectForRead() {return true;}
virtual bool SelectForWrite();
int SendOutboundData (const char*, unsigned long);
int SendOutboundDatagram (const char*, unsigned long, const char*, int);
// Do we have any data to write? This is used by ShouldDelete.
virtual int GetOutboundDataSize() {return OutboundDataSize;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len);
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); };
virtual uint64_t GetCommInactivityTimeout();
virtual int SetCommInactivityTimeout (uint64_t value);
protected:
struct OutboundPage {
OutboundPage (const char *b, int l, struct sockaddr_in6 f, int o=0): Buffer(b), Length(l), Offset(o), From(f) {}
void Free() {if (Buffer) free (const_cast<char*>(Buffer)); }
const char *Buffer;
int Length;
int Offset;
struct sockaddr_in6 From;
};
std::deque<OutboundPage> OutboundPages;
int OutboundDataSize;
struct sockaddr_in6 ReturnAddress;
};
/************************
class AcceptorDescriptor
************************/
class AcceptorDescriptor: public EventableDescriptor
{
public:
AcceptorDescriptor (SOCKET, EventMachine_t*);
virtual ~AcceptorDescriptor();
virtual void Read();
virtual void Write();
virtual void Heartbeat();
virtual bool SelectForRead() {return true;}
virtual bool SelectForWrite() {return false;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); };
static void StopAcceptor (const uintptr_t binding);
};
/********************
class PipeDescriptor
********************/
#ifdef OS_UNIX
class PipeDescriptor: public EventableDescriptor
{
public:
PipeDescriptor (SOCKET, pid_t, EventMachine_t*);
virtual ~PipeDescriptor() NO_EXCEPT_FALSE;
virtual void Read();
virtual void Write();
virtual void Heartbeat();
virtual bool SelectForRead();
virtual bool SelectForWrite();
int SendOutboundData (const char*, unsigned long);
virtual int GetOutboundDataSize() {return OutboundDataSize;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }
virtual bool GetSubprocessPid (pid_t*);
protected:
struct OutboundPage {
OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
void Free() {if (Buffer) free (const_cast<char*>(Buffer)); }
const char *Buffer;
int Length;
int Offset;
};
protected:
bool bReadAttemptedAfterClose;
std::deque<OutboundPage> OutboundPages;
int OutboundDataSize;
pid_t SubprocessPid;
private:
void _DispatchInboundData (const char *buffer, int size);
};
#endif // OS_UNIX
/************************
class KeyboardDescriptor
************************/
class KeyboardDescriptor: public EventableDescriptor
{
public:
KeyboardDescriptor (EventMachine_t*);
virtual ~KeyboardDescriptor();
virtual void Read();
virtual void Write();
virtual void Heartbeat();
virtual bool SelectForRead() {return true;}
virtual bool SelectForWrite() {return false;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }
protected:
bool bReadAttemptedAfterClose;
private:
void _DispatchInboundData (const char *buffer, int size);
};
/***********************
class InotifyDescriptor
************************/
class InotifyDescriptor: public EventableDescriptor
{
public:
InotifyDescriptor (EventMachine_t*);
virtual ~InotifyDescriptor();
void Read();
void Write();
virtual void Heartbeat() {}
virtual bool SelectForRead() {return true;}
virtual bool SelectForWrite() {return false;}
virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return false; }
virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return false; }
};
#endif // __EventableDescriptor__H_
File diff suppressed because it is too large Load Diff
+308
View File
@@ -0,0 +1,308 @@
/*****************************************************************************
$Id$
File: em.h
Date: 06Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __EventMachine__H_
#define __EventMachine__H_
#ifdef BUILD_FOR_RUBY
#include <ruby.h>
#ifdef HAVE_RB_THREAD_FD_SELECT
#define EmSelect rb_thread_fd_select
#else
// ruby 1.9.1 and below
#define EmSelect rb_thread_select
#endif
#ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
#include <ruby/thread.h>
#endif
#ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
#include <ruby/io.h>
#endif
#if defined(HAVE_RB_TRAP_IMMEDIATE)
#include <rubysig.h>
#elif defined(HAVE_RB_ENABLE_INTERRUPT)
extern "C" {
void rb_enable_interrupt(void);
void rb_disable_interrupt(void);
}
#define TRAP_BEG rb_enable_interrupt()
#define TRAP_END do { rb_disable_interrupt(); rb_thread_check_ints(); } while(0)
#else
#define TRAP_BEG
#define TRAP_END
#endif
// 1.9.0 compat
#ifndef RUBY_UBF_IO
#define RUBY_UBF_IO RB_UBF_DFL
#endif
#ifndef RSTRING_PTR
#define RSTRING_PTR(str) RSTRING(str)->ptr
#endif
#ifndef RSTRING_LEN
#define RSTRING_LEN(str) RSTRING(str)->len
#endif
#ifndef RSTRING_LENINT
#define RSTRING_LENINT(str) RSTRING_LEN(str)
#endif
#else
#define EmSelect select
#endif
#if !defined(HAVE_TYPE_RB_FDSET_T)
#define fd_check(n) (((n) < FD_SETSIZE) ? 1 : 0*fprintf(stderr, "fd %d too large for select\n", (n)))
// These definitions are cribbed from include/ruby/intern.h in Ruby 1.9.3,
// with this change: any macros that read or write the nth element of an
// fdset first call fd_check to make sure n is in bounds.
typedef fd_set rb_fdset_t;
#define rb_fd_zero(f) FD_ZERO(f)
#define rb_fd_set(n, f) do { if (fd_check(n)) FD_SET((n), (f)); } while(0)
#define rb_fd_clr(n, f) do { if (fd_check(n)) FD_CLR((n), (f)); } while(0)
#define rb_fd_isset(n, f) (fd_check(n) ? FD_ISSET((n), (f)) : 0)
#define rb_fd_copy(d, s, n) (*(d) = *(s))
#define rb_fd_dup(d, s) (*(d) = *(s))
#define rb_fd_resize(n, f) ((void)(f))
#define rb_fd_ptr(f) (f)
#define rb_fd_init(f) FD_ZERO(f)
#define rb_fd_init_copy(d, s) (*(d) = *(s))
#define rb_fd_term(f) ((void)(f))
#define rb_fd_max(f) FD_SETSIZE
#define rb_fd_select(n, rfds, wfds, efds, timeout) \
select(fd_check((n)-1) ? (n) : FD_SETSIZE, (rfds), (wfds), (efds), (timeout))
#define rb_thread_fd_select(n, rfds, wfds, efds, timeout) \
rb_thread_select(fd_check((n)-1) ? (n) : FD_SETSIZE, (rfds), (wfds), (efds), (timeout))
#endif
// This Solaris fix is adapted from eval_intern.h in Ruby 1.9.3:
// Solaris sys/select.h switches select to select_large_fdset to support larger
// file descriptors if FD_SETSIZE is larger than 1024 on 32bit environment.
// But Ruby doesn't change FD_SETSIZE because fd_set is allocated dynamically.
// So following definition is required to use select_large_fdset.
#ifdef HAVE_SELECT_LARGE_FDSET
#define select(n, r, w, e, t) select_large_fdset((n), (r), (w), (e), (t))
extern "C" {
int select_large_fdset(int, fd_set *, fd_set *, fd_set *, struct timeval *);
}
#endif
class EventableDescriptor;
class InotifyDescriptor;
struct SelectData_t;
/*************
enum Poller_t
*************/
enum Poller_t {
Poller_Default, // typically Select
Poller_Epoll,
Poller_Kqueue
};
/********************
class EventMachine_t
********************/
class EventMachine_t
{
public:
static int GetMaxTimerCount();
static void SetMaxTimerCount (int);
static int GetSimultaneousAcceptCount();
static void SetSimultaneousAcceptCount (int);
public:
EventMachine_t (EMCallback, Poller_t);
virtual ~EventMachine_t();
bool RunOnce();
void Run();
void ScheduleHalt();
bool Stopping();
void SignalLoopBreaker();
const uintptr_t InstallOneshotTimer (uint64_t);
const uintptr_t ConnectToServer (const char *, int, const char *, int);
const uintptr_t ConnectToUnixServer (const char *);
const uintptr_t CreateTcpServer (const char *, int);
const uintptr_t OpenDatagramSocket (const char *, int);
const uintptr_t CreateUnixDomainServer (const char*);
const uintptr_t AttachSD (SOCKET);
const uintptr_t OpenKeyboard();
//const char *Popen (const char*, const char*);
const uintptr_t Socketpair (char* const*);
void Add (EventableDescriptor*);
void Modify (EventableDescriptor*);
void Deregister (EventableDescriptor*);
const uintptr_t AttachFD (SOCKET, bool);
int DetachFD (EventableDescriptor*);
void ArmKqueueWriter (EventableDescriptor*);
void ArmKqueueReader (EventableDescriptor*);
void SetTimerQuantum (int);
static void SetuidString (const char*);
static int SetRlimitNofile (int);
pid_t SubprocessPid;
int SubprocessExitStatus;
int GetConnectionCount();
float GetHeartbeatInterval();
int SetHeartbeatInterval(float);
const uintptr_t WatchFile (const char*);
void UnwatchFile (int);
void UnwatchFile (const uintptr_t);
#ifdef HAVE_KQUEUE
void _HandleKqueueFileEvent (struct kevent*);
void _RegisterKqueueFileEvent(int);
#endif
const uintptr_t WatchPid (int);
void UnwatchPid (int);
void UnwatchPid (const uintptr_t);
#ifdef HAVE_KQUEUE
void _HandleKqueuePidEvent (struct kevent*);
#endif
uint64_t GetCurrentLoopTime() { return MyCurrentLoopTime; }
void QueueHeartbeat(EventableDescriptor*);
void ClearHeartbeat(uint64_t, EventableDescriptor*);
uint64_t GetRealTime();
Poller_t GetPoller() { return Poller; }
static int name2address (const char *server, int port, int socktype, struct sockaddr *addr, size_t *addr_len);
private:
void _RunTimers();
void _UpdateTime();
void _AddNewDescriptors();
void _ModifyDescriptors();
void _InitializeLoopBreaker();
void _CleanupSockets();
void _RunSelectOnce();
void _RunEpollOnce();
void _RunKqueueOnce();
void _ModifyEpollEvent (EventableDescriptor*);
void _DispatchHeartbeats();
timeval _TimeTilNextEvent();
void _CleanBadDescriptors();
public:
void _ReadLoopBreaker();
void _ReadInotifyEvents();
int NumCloseScheduled;
private:
enum {
MaxEpollDescriptors = 64*1024,
MaxEvents = 4096
};
int HeartbeatInterval;
EMCallback EventCallback;
class Timer_t: public Bindable_t {
};
std::multimap<uint64_t, Timer_t> Timers;
std::multimap<uint64_t, EventableDescriptor*> Heartbeats;
std::map<int, Bindable_t*> Files;
std::map<int, Bindable_t*> Pids;
std::vector<EventableDescriptor*> Descriptors;
std::vector<EventableDescriptor*> NewDescriptors;
std::set<EventableDescriptor*> ModifiedDescriptors;
SOCKET LoopBreakerReader;
SOCKET LoopBreakerWriter;
#ifdef OS_WIN32
struct sockaddr_in LoopBreakerTarget;
#endif
timeval Quantum;
uint64_t MyCurrentLoopTime;
#ifdef OS_WIN32
unsigned TickCountTickover;
unsigned LastTickCount;
#endif
#ifdef OS_DARWIN
mach_timebase_info_data_t mach_timebase;
#endif
private:
bool bTerminateSignalReceived;
SelectData_t *SelectData;
Poller_t Poller;
int epfd; // Epoll file-descriptor
#ifdef HAVE_EPOLL
struct epoll_event epoll_events [MaxEvents];
#endif
int kqfd; // Kqueue file-descriptor
#ifdef HAVE_KQUEUE
struct kevent Karray [MaxEvents];
#endif
#ifdef HAVE_INOTIFY
InotifyDescriptor *inotify; // pollable descriptor for our inotify instance
#endif
};
/*******************
struct SelectData_t
*******************/
struct SelectData_t
{
SelectData_t();
~SelectData_t();
int _Select();
void _Clear();
SOCKET maxsocket;
rb_fdset_t fdreads;
rb_fdset_t fdwrites;
rb_fdset_t fderrors;
timeval tv;
int nSockets;
};
#endif // __EventMachine__H_
@@ -0,0 +1,143 @@
/*****************************************************************************
$Id$
File: eventmachine.h
Date: 15Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __EVMA_EventMachine__H_
#define __EVMA_EventMachine__H_
#if __cplusplus
extern "C" {
#endif
enum { // Event names
EM_TIMER_FIRED = 100,
EM_CONNECTION_READ = 101,
EM_CONNECTION_UNBOUND = 102,
EM_CONNECTION_ACCEPTED = 103,
EM_CONNECTION_COMPLETED = 104,
EM_LOOPBREAK_SIGNAL = 105,
EM_CONNECTION_NOTIFY_READABLE = 106,
EM_CONNECTION_NOTIFY_WRITABLE = 107,
EM_SSL_HANDSHAKE_COMPLETED = 108,
EM_SSL_VERIFY = 109,
EM_PROXY_TARGET_UNBOUND = 110,
EM_PROXY_COMPLETED = 111
};
enum { // SSL/TLS Protocols
EM_PROTO_SSLv2 = 2,
EM_PROTO_SSLv3 = 4,
EM_PROTO_TLSv1 = 8,
EM_PROTO_TLSv1_1 = 16,
EM_PROTO_TLSv1_2 = 32
};
void evma_initialize_library (EMCallback);
bool evma_run_machine_once();
void evma_run_machine();
void evma_release_library();
const uintptr_t evma_install_oneshot_timer (uint64_t milliseconds);
const uintptr_t evma_connect_to_server (const char *bind_addr, int bind_port, const char *server, int port);
const uintptr_t evma_connect_to_unix_server (const char *server);
const uintptr_t evma_attach_fd (int file_descriptor, int watch_mode);
int evma_detach_fd (const uintptr_t binding);
int evma_get_file_descriptor (const uintptr_t binding);
int evma_is_notify_readable (const uintptr_t binding);
void evma_set_notify_readable (const uintptr_t binding, int mode);
int evma_is_notify_writable (const uintptr_t binding);
void evma_set_notify_writable (const uintptr_t binding, int mode);
int evma_pause(const uintptr_t binding);
int evma_is_paused(const uintptr_t binding);
int evma_resume(const uintptr_t binding);
int evma_num_close_scheduled();
void evma_stop_tcp_server (const uintptr_t binding);
const uintptr_t evma_create_tcp_server (const char *address, int port);
const uintptr_t evma_create_unix_domain_server (const char *filename);
const uintptr_t evma_attach_sd (int sd);
const uintptr_t evma_open_datagram_socket (const char *server, int port);
const uintptr_t evma_open_keyboard();
void evma_set_tls_parms (const uintptr_t binding, const char *privatekey_filename, const char *certchain_filenane, int verify_peer, int fail_if_no_peer_cert, const char *sni_hostname, const char *cipherlist, const char *ecdh_curve, const char *dhparam, int protocols);
void evma_start_tls (const uintptr_t binding);
#ifdef WITH_SSL
X509 *evma_get_peer_cert (const uintptr_t binding);
int evma_get_cipher_bits (const uintptr_t binding);
const char *evma_get_cipher_name (const uintptr_t binding);
const char *evma_get_cipher_protocol (const uintptr_t binding);
const char *evma_get_sni_hostname (const uintptr_t binding);
void evma_accept_ssl_peer (const uintptr_t binding);
#endif
int evma_get_peername (const uintptr_t binding, struct sockaddr*, socklen_t*);
int evma_get_sockname (const uintptr_t binding, struct sockaddr*, socklen_t*);
int evma_get_subprocess_pid (const uintptr_t binding, pid_t*);
int evma_get_subprocess_status (const uintptr_t binding, int*);
int evma_get_connection_count();
int evma_send_data_to_connection (const uintptr_t binding, const char *data, int data_length);
int evma_send_datagram (const uintptr_t binding, const char *data, int data_length, const char *address, int port);
float evma_get_comm_inactivity_timeout (const uintptr_t binding);
int evma_set_comm_inactivity_timeout (const uintptr_t binding, float value);
float evma_get_pending_connect_timeout (const uintptr_t binding);
int evma_set_pending_connect_timeout (const uintptr_t binding, float value);
int evma_get_outbound_data_size (const uintptr_t binding);
uint64_t evma_get_last_activity_time (const uintptr_t binding);
int evma_send_file_data_to_connection (const uintptr_t binding, const char *filename);
void evma_close_connection (const uintptr_t binding, int after_writing);
int evma_report_connection_error_status (const uintptr_t binding);
void evma_signal_loopbreak();
void evma_set_timer_quantum (int);
int evma_get_max_timer_count();
void evma_set_max_timer_count (int);
int evma_get_simultaneous_accept_count();
void evma_set_simultaneous_accept_count (int);
void evma_setuid_string (const char *username);
void evma_stop_machine();
bool evma_stopping();
float evma_get_heartbeat_interval();
int evma_set_heartbeat_interval(float);
const uintptr_t evma_popen (char * const*cmd_strings);
const uintptr_t evma_watch_filename (const char *fname);
void evma_unwatch_filename (const uintptr_t binding);
const uintptr_t evma_watch_pid (int);
void evma_unwatch_pid (const uintptr_t binding);
void evma_start_proxy(const uintptr_t from, const uintptr_t to, const unsigned long bufsize, const unsigned long length);
void evma_stop_proxy(const uintptr_t from);
unsigned long evma_proxied_bytes(const uintptr_t from);
int evma_set_rlimit_nofile (int n_files);
void evma_set_epoll (int use);
void evma_set_kqueue (int use);
uint64_t evma_get_current_loop_time();
#if __cplusplus
}
#endif
#endif // __EventMachine__H_
+270
View File
@@ -0,0 +1,270 @@
require 'fileutils'
require 'mkmf'
# Eager check devs tools
have_devel? if respond_to?(:have_devel?)
def check_libs libs = [], fatal = false
libs.all? { |lib| have_library(lib) || (abort("could not find library: #{lib}") if fatal) }
end
def check_heads heads = [], fatal = false
heads.all? { |head| have_header(head) || (abort("could not find header: #{head}") if fatal)}
end
def add_define(name)
$defs.push("-D#{name}")
end
##
# OpenSSL:
# override append_library, so it actually appends (instead of prepending)
# this fixes issues with linking ssl, since libcrypto depends on symbols in libssl
def append_library(libs, lib)
libs + " " + format(LIBARG, lib)
end
SSL_HEADS = %w(openssl/ssl.h openssl/err.h)
SSL_LIBS = %w(crypto ssl)
# OpenSSL 1.1.0 and above for Windows use the Unix library names
# OpenSSL 0.9.8 and 1.0.x for Windows use the *eay32 library names
SSL_LIBS_WIN = RUBY_PLATFORM =~ /mswin|mingw|bccwin/ ? %w(ssleay32 libeay32) : []
def dir_config_wrapper(pretty_name, name, idefault=nil, ldefault=nil)
inc, lib = dir_config(name, idefault, ldefault)
if inc && lib
# TODO: Remove when 2.0.0 is the minimum supported version
# Ruby versions not incorporating the mkmf fix at
# https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717
# do not properly search for lib directories, and must be corrected
unless lib && lib[-3, 3] == 'lib'
@libdir_basename = 'lib'
inc, lib = dir_config(name, idefault, ldefault)
end
unless idefault && ldefault
abort "-----\nCannot find #{pretty_name} include path #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
abort "-----\nCannot find #{pretty_name} library path #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
warn "-----\nUsing #{pretty_name} in path #{File.dirname inc}\n-----"
end
true
end
end
def dir_config_search(pretty_name, name, paths, &b)
paths.each do |p|
if dir_config_wrapper('OpenSSL', 'ssl', p + '/include', p + '/lib') && yield
warn "-----\nFound #{pretty_name} in path #{p}\n-----"
return true
end
end
false
end
def pkg_config_wrapper(pretty_name, name)
cflags, ldflags, libs = pkg_config(name)
unless [cflags, ldflags, libs].any?(&:nil?) || [cflags, ldflags, libs].any?(&:empty?)
warn "-----\nUsing #{pretty_name} from pkg-config #{cflags} && #{ldflags} && #{libs}\n-----"
true
end
end
if ENV['CROSS_COMPILING']
openssl_version = ENV.fetch("OPENSSL_VERSION", "1.0.2e")
openssl_dir = File.expand_path("~/.rake-compiler/builds/openssl-#{openssl_version}/")
if File.exist?(openssl_dir)
FileUtils.mkdir_p Dir.pwd+"/openssl/"
FileUtils.cp Dir[openssl_dir+"/include/openssl/*.h"], Dir.pwd+"/openssl/", :verbose => true
FileUtils.cp Dir[openssl_dir+"/lib*.a"], Dir.pwd, :verbose => true
$INCFLAGS << " -I#{Dir.pwd}" # for the openssl headers
add_define "WITH_SSL"
else
STDERR.puts
STDERR.puts "**************************************************************************************"
STDERR.puts "**** Cross-compiled OpenSSL not found"
STDERR.puts "**** Run: hg clone http://bitbucket.org/ged/ruby-pg && cd ruby-pg && rake openssl_libs"
STDERR.puts "**************************************************************************************"
STDERR.puts
end
elsif dir_config_wrapper('OpenSSL', 'ssl')
# If the user has provided a --with-ssl-dir argument, we must respect it or fail.
add_define 'WITH_SSL' if (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS)
elsif pkg_config_wrapper('OpenSSL', 'openssl')
# If we can detect OpenSSL by pkg-config, use it as the next-best option
add_define 'WITH_SSL' if (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS)
elsif (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS)
# If we don't even need any options to find a usable OpenSSL, go with it
add_define 'WITH_SSL'
elsif dir_config_search('OpenSSL', 'ssl', ['/usr/local', '/opt/local', '/usr/local/opt/openssl']) do
(check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS)
end
# Finally, look for OpenSSL in alternate locations including MacPorts and HomeBrew
add_define 'WITH_SSL'
end
add_define 'BUILD_FOR_RUBY'
# Ruby features:
have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
have_func('rb_thread_blocking_region')
have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
have_func('rb_thread_fd_select')
have_type('rb_fdset_t', 'ruby/intern.h')
have_func('rb_wait_for_single_fd')
have_func('rb_enable_interrupt')
have_func('rb_time_new')
# System features:
add_define('HAVE_INOTIFY') if inotify = have_func('inotify_init', 'sys/inotify.h')
add_define('HAVE_OLD_INOTIFY') if !inotify && have_macro('__NR_inotify_init', 'sys/syscall.h')
have_func('writev', 'sys/uio.h')
have_func('pipe2', 'unistd.h')
have_func('accept4', 'sys/socket.h')
have_const('SOCK_CLOEXEC', 'sys/socket.h')
# Minor platform details between *nix and Windows:
if RUBY_PLATFORM =~ /(mswin|mingw|bccwin)/
GNU_CHAIN = ENV['CROSS_COMPILING'] || $1 == 'mingw'
OS_WIN32 = true
add_define "OS_WIN32"
else
GNU_CHAIN = true
OS_UNIX = true
add_define 'OS_UNIX'
add_define "HAVE_KQUEUE" if have_header("sys/event.h") && have_header("sys/queue.h")
end
# Adjust number of file descriptors (FD) on Windows
if RbConfig::CONFIG["host_os"] =~ /mingw/
found = RbConfig::CONFIG.values_at("CFLAGS", "CPPFLAGS").
any? { |v| v.include?("FD_SETSIZE") }
add_define "FD_SETSIZE=32767" unless found
end
# Main platform invariances:
case RUBY_PLATFORM
when /mswin32/, /mingw32/, /bccwin32/
check_heads(%w[windows.h winsock.h], true)
check_libs(%w[kernel32 rpcrt4 gdi32], true)
if GNU_CHAIN
CONFIG['LDSHAREDXX'] = "$(CXX) -shared -static-libgcc -static-libstdc++"
else
$defs.push "-EHs"
$defs.push "-GR"
end
# Newer versions of Ruby already define _WIN32_WINNT, which is needed
# to get access to newer POSIX networking functions (e.g. getaddrinfo)
add_define '_WIN32_WINNT=0x0501' unless have_func('getaddrinfo')
when /solaris/
add_define 'OS_SOLARIS8'
check_libs(%w[nsl socket], true)
# If Ruby was compiled for 32-bits, then select() can only handle 1024 fds
# There is an alternate function, select_large_fdset, that supports more.
have_func('select_large_fdset', 'sys/select.h')
if CONFIG['CC'] == 'cc' && (
`cc -flags 2>&1` =~ /Sun/ || # detect SUNWspro compiler
`cc -V 2>&1` =~ /Sun/ # detect Solaris Studio compiler
)
# SUN CHAIN
add_define 'CC_SUNWspro'
$preload = ["\nCXX = CC"] # hack a CXX= line into the makefile
$CFLAGS = CONFIG['CFLAGS'] = "-KPIC"
CONFIG['CCDLFLAGS'] = "-KPIC"
CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd"
CONFIG['LDSHAREDXX'] = "$(CXX) -G -KPIC -lCstd"
else
# GNU CHAIN
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
end
when /openbsd/
# OpenBSD branch contributed by Guillaume Sellier.
# on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs
CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC"
CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC"
when /darwin/
add_define 'OS_DARWIN'
# on Unix we need a g++ link, not gcc.
# Ff line contributed by Daniel Harple.
CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ')
when /linux/
add_define 'HAVE_EPOLL' if have_func('epoll_create', 'sys/epoll.h')
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
when /aix/
CONFIG['LDSHARED'] = "$(CXX) -Wl,-bstatic -Wl,-bdynamic -Wl,-G -Wl,-brtl"
when /cygwin/
# For rubies built with Cygwin, CXX may be set to CC, which is just
# a wrapper for gcc.
# This will compile, but it will not link to the C++ std library.
# Explicitly set CXX to use g++.
CONFIG['CXX'] = "g++"
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
else
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
end
# Platform-specific time functions
if have_func('clock_gettime')
# clock_gettime is POSIX, but the monotonic clocks are not
have_const('CLOCK_MONOTONIC_RAW', 'time.h') # Linux
have_const('CLOCK_MONOTONIC', 'time.h') # Linux, Solaris, BSDs
else
have_func('gethrtime') # Older Solaris and HP-UX
end
# Hack so that try_link will test with a C++ compiler instead of a C compiler
TRY_LINK.sub!('$(CC)', '$(CXX)')
# This is our wishlist. We use whichever flags work on the host.
# In the future, add -Werror to make sure all warnings are resolved.
# deprecated-declarations are used in OS X OpenSSL
# ignored-qualifiers are used by the Bindings (would-be void *)
# unused-result because GCC 4.6 no longer silences (void) ignore_this(function)
# address because on Windows, rb_fd_select checks if &fds is non-NULL, which it cannot be
%w(
-Wall
-Wextra
-Wno-deprecated-declarations
-Wno-ignored-qualifiers
-Wno-unused-result
-Wno-address
).select do |flag|
try_link('int main() {return 0;}', flag)
end.each do |flag|
CONFIG['CXXFLAGS'] << ' ' << flag
end
puts "CXXFLAGS=#{CONFIG['CXXFLAGS']}"
# Solaris C++ compiler doesn't have make_pair()
add_define 'HAVE_MAKE_PAIR' if try_link(<<SRC, '-lstdc++')
#include <utility>
using namespace std;
int main(){ pair<const int,int> tuple = make_pair(1,2); }
SRC
TRY_LINK.sub!('$(CXX)', '$(CC)')
create_makefile "rubyeventmachine"
@@ -0,0 +1,269 @@
SHELL = /bin/sh
# V=0 quiet, V=1 verbose. other values don't work.
V = 0
V0 = $(V:0=)
Q1 = $(V:1=)
Q = $(Q1:0=@)
ECHO1 = $(V:1=@ :)
ECHO = $(ECHO1:0=@ echo)
NULLCMD = :
#### Start of system configuration section. ####
srcdir = .
topdir = /usr/include/ruby-3.2.0
hdrdir = $(topdir)
arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0
PATH_SEPARATOR = :
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
prefix = $(DESTDIR)/usr
rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME)
rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME)
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
exec_prefix = $(prefix)
vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby
sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby
rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME)
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
sitehdrdir = $(rubyhdrdir)/site_ruby
rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version)
vendorlibdir = $(vendordir)/$(ruby_version)
vendordir = $(rubylibprefix)/vendor_ruby
sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby
sitelibdir = $(sitedir)/$(ruby_version)
sitedir = $(DESTDIR)/usr/local/lib/site_ruby
rubyarchdir = $(rubyarchprefix)/$(ruby_version)
rubylibdir = $(rubylibprefix)/$(ruby_version)
sitearchincludedir = $(includedir)/$(sitearch)
archincludedir = $(includedir)/$(arch)
sitearchlibdir = $(libdir)/$(sitearch)
archlibdir = $(libdir)/$(arch)
ridir = $(datarootdir)/$(RI_BASE_NAME)
mandir = $(datarootdir)/man
localedir = $(datarootdir)/locale
libdir = $(exec_prefix)/lib
psdir = $(docdir)
pdfdir = $(docdir)
dvidir = $(docdir)
htmldir = $(docdir)
infodir = $(datarootdir)/info
docdir = $(datarootdir)/doc/$(PACKAGE)
oldincludedir = $(DESTDIR)/usr/include
includedir = $(prefix)/include
runstatedir = $(DESTDIR)/var/run
localstatedir = $(DESTDIR)/var
sharedstatedir = $(prefix)/com
sysconfdir = $(DESTDIR)/etc
datadir = $(datarootdir)
datarootdir = $(prefix)/share
libexecdir = $(exec_prefix)/libexec
sbindir = $(exec_prefix)/sbin
bindir = $(exec_prefix)/bin
archdir = $(rubyarchdir)
CC_WRAPPER =
CC = x86_64-linux-gnu-gcc
CXX = x86_64-linux-gnu-g++
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS)
empty =
OUTFLAG = -o $(empty)
COUTFLAG = -o $(empty)
CSRCFLAG = $(empty)
RUBY_EXTCONF_H =
cflags = $(optflags) $(debugflags) $(warnflags)
cxxflags =
optflags = -O3 -fno-fast-math
debugflags = -ggdb3
warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef
cppflags =
CCDLFLAGS = -fPIC
CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 -fPIC $(ARCH_FLAG)
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
DEFS =
CPPFLAGS = -DBUILD_FOR_RUBY -DOS_UNIX -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags)
CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 $(ARCH_FLAG)
ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed
dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now
ARCH_FLAG =
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
LDSHARED = $(CXX) -shared
LDSHAREDXX = $(CXX) -shared
AR = x86_64-linux-gnu-gcc-ar
EXEEXT =
RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2
RUBY_SO_NAME = ruby-3.2
RUBYW_INSTALL_NAME =
RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
RUBYW_BASE_NAME = rubyw
RUBY_BASE_NAME = ruby
arch = x86_64-linux-gnu
sitearch = $(arch)
ruby_version = 3.2.0
ruby = $(bindir)/$(RUBY_BASE_NAME)3.2
RUBY = $(ruby)
BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2
ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
RM = rm -f
RM_RF = rm -fr
RMDIRS = rmdir --ignore-fail-on-non-empty -p
MAKEDIRS = /bin/mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
COPY = cp
TOUCH = exit >
#### End of system configuration section. ####
preload =
libpath = . $(archlibdir)
LIBPATH = -L. -L$(archlibdir)
DEFFILE =
CLEANFILES = mkmf.log
DISTCLEANFILES =
DISTCLEANDIRS =
extout =
extout_prefix =
target_prefix =
LOCAL_LIBS =
LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc
ORIG_SRCS = mapper.cpp rubymain.cpp
SRCS = $(ORIG_SRCS)
OBJS = mapper.o rubymain.o
HDRS = $(srcdir)/mapper.h
LOCAL_HDRS =
TARGET = fastfilereaderext
TARGET_NAME = fastfilereaderext
TARGET_ENTRY = Init_$(TARGET_NAME)
DLLIB = $(TARGET).so
EXTSTATIC =
STATIC_LIB =
TIMESTAMP_DIR = .
BINDIR = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
HDRDIR = $(sitehdrdir)$(target_prefix)
ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
TARGET_SO_DIR =
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
CLEANLIBS = $(TARGET_SO) false
CLEANOBJS = $(OBJS) *.bak
TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time
all: $(DLLIB)
static: $(STATIC_LIB)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
clean-static::
clean-rb-default::
clean-rb::
clean-so::
clean: clean-so clean-static clean-rb-default clean-rb
-$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
distclean-rb-default::
distclean-rb::
distclean-so::
distclean-static::
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
-$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
realclean: distclean
install: install-so install-rb
install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
clean-static::
-$(Q)$(RM) $(STATIC_LIB)
install-rb: pre-install-rb do-install-rb install-rb-default
install-rb-default: pre-install-rb-default do-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
do-install-rb:
do-install-rb-default:
pre-install-rb-default:
@$(NULLCMD)
$(TARGET_SO_DIR_TIMESTAMP):
$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
$(Q) $(TOUCH) $@
site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb
.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
.cc.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cc.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.mm.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.mm.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cxx.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cxx.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cpp.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cpp.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.c.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.c.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.m.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.m.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
$(TARGET_SO): $(OBJS) Makefile
$(ECHO) linking shared-object $(DLLIB)
-$(Q)$(RM) $(@)
$(Q) $(LDSHAREDXX) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
$(OBJS): $(HDRS) $(ruby_headers)
@@ -0,0 +1,109 @@
require 'mkmf'
def check_libs libs = [], fatal = false
libs.all? { |lib| have_library(lib) || (abort("could not find library: #{lib}") if fatal) }
end
def check_heads heads = [], fatal = false
heads.all? { |head| have_header(head) || (abort("could not find header: #{head}") if fatal)}
end
def add_define(name)
$defs.push("-D#{name}")
end
# Eager check devs tools
have_devel? if respond_to?(:have_devel?)
add_define 'BUILD_FOR_RUBY'
# Minor platform details between *nix and Windows:
if RUBY_PLATFORM =~ /(mswin|mingw|bccwin)/
GNU_CHAIN = ENV['CROSS_COMPILING'] || $1 == 'mingw'
OS_WIN32 = true
add_define "OS_WIN32"
else
GNU_CHAIN = true
OS_UNIX = true
add_define 'OS_UNIX'
end
# Adjust number of file descriptors (FD) on Windows
if RbConfig::CONFIG["host_os"] =~ /mingw/
found = RbConfig::CONFIG.values_at("CFLAGS", "CPPFLAGS").
any? { |v| v.include?("FD_SETSIZE") }
add_define "FD_SETSIZE=32767" unless found
end
# Main platform invariances:
case RUBY_PLATFORM
when /mswin32/, /mingw32/, /bccwin32/
check_heads(%w[windows.h winsock.h], true)
check_libs(%w[kernel32 rpcrt4 gdi32], true)
if GNU_CHAIN
CONFIG['LDSHAREDXX'] = "$(CXX) -shared -static-libgcc -static-libstdc++"
else
$defs.push "-EHs"
$defs.push "-GR"
end
when /solaris/
add_define 'OS_SOLARIS8'
check_libs(%w[nsl socket], true)
if CONFIG['CC'] == 'cc' && (
`cc -flags 2>&1` =~ /Sun/ || # detect SUNWspro compiler
`cc -V 2>&1` =~ /Sun/ # detect Solaris Studio compiler
)
# SUN CHAIN
add_define 'CC_SUNWspro'
$preload = ["\nCXX = CC"] # hack a CXX= line into the makefile
$CFLAGS = CONFIG['CFLAGS'] = "-KPIC"
CONFIG['CCDLFLAGS'] = "-KPIC"
CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd"
CONFIG['LDSHAREDXX'] = "$(CXX) -G -KPIC -lCstd"
else
# GNU CHAIN
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
end
when /openbsd/
# OpenBSD branch contributed by Guillaume Sellier.
# on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs
CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC"
CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC"
when /darwin/
# on Unix we need a g++ link, not gcc.
# Ff line contributed by Daniel Harple.
CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ')
when /linux/
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
when /aix/
CONFIG['LDSHARED'] = "$(CXX) -Wl,-bstatic -Wl,-bdynamic -Wl,-G -Wl,-brtl"
when /cygwin/
# For rubies built with Cygwin, CXX may be set to CC, which is just
# a wrapper for gcc.
# This will compile, but it will not link to the C++ std library.
# Explicitly set CXX to use g++.
CONFIG['CXX'] = "g++"
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
else
# on Unix we need a g++ link, not gcc.
CONFIG['LDSHARED'] = "$(CXX) -shared"
end
create_makefile "fastfilereaderext"
@@ -0,0 +1,214 @@
/*****************************************************************************
$Id: mapper.cpp 4527 2007-07-04 10:21:34Z francis $
File: mapper.cpp
Date: 02Jul07
Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved.
Gmail: garbagecat10
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
//////////////////////////////////////////////////////////////////////
// UNIX implementation
//////////////////////////////////////////////////////////////////////
#ifdef OS_UNIX
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>
#include <stdexcept>
#include "mapper.h"
/******************
Mapper_t::Mapper_t
******************/
Mapper_t::Mapper_t (const std::string &filename)
{
/* We ASSUME we can open the file.
* (More precisely, we assume someone else checked before we got here.)
*/
Fd = open (filename.c_str(), O_RDONLY);
if (Fd < 0)
throw std::runtime_error (strerror (errno));
struct stat st;
if (fstat (Fd, &st))
throw std::runtime_error (strerror (errno));
FileSize = st.st_size;
#ifdef OS_WIN32
MapPoint = (char*) mmap (0, FileSize, PROT_READ, MAP_SHARED, Fd, 0);
#else
MapPoint = (const char*) mmap (0, FileSize, PROT_READ, MAP_SHARED, Fd, 0);
#endif
if (MapPoint == MAP_FAILED)
throw std::runtime_error (strerror (errno));
}
/*******************
Mapper_t::~Mapper_t
*******************/
Mapper_t::~Mapper_t()
{
Close();
}
/***************
Mapper_t::Close
***************/
void Mapper_t::Close()
{
// Can be called multiple times.
// Calls to GetChunk are invalid after a call to Close.
if (MapPoint) {
#ifdef CC_SUNWspro
// TODO: The void * cast works fine on Solaris 11, but
// I don't know at what point that changed from older Solaris.
munmap ((char*)MapPoint, FileSize);
#else
munmap ((void*)MapPoint, FileSize);
#endif
MapPoint = NULL;
}
if (Fd >= 0) {
close (Fd);
Fd = -1;
}
}
/******************
Mapper_t::GetChunk
******************/
const char *Mapper_t::GetChunk (unsigned start)
{
return MapPoint + start;
}
#endif // OS_UNIX
//////////////////////////////////////////////////////////////////////
// WINDOWS implementation
//////////////////////////////////////////////////////////////////////
#ifdef OS_WIN32
#include <windows.h>
#include <iostream>
#include <string>
#include <stdexcept>
#include "mapper.h"
/******************
Mapper_t::Mapper_t
******************/
Mapper_t::Mapper_t (const std::string &filename)
{
/* We ASSUME we can open the file.
* (More precisely, we assume someone else checked before we got here.)
*/
hFile = INVALID_HANDLE_VALUE;
hMapping = NULL;
MapPoint = NULL;
FileSize = 0;
hFile = CreateFile (filename.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
throw std::runtime_error ("File not found");
BY_HANDLE_FILE_INFORMATION i;
if (GetFileInformationByHandle (hFile, &i))
FileSize = i.nFileSizeLow;
hMapping = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (!hMapping)
throw std::runtime_error ("File not mapped");
#ifdef OS_WIN32
MapPoint = (char*) MapViewOfFile (hMapping, FILE_MAP_WRITE, 0, 0, 0);
#else
MapPoint = (const char*) MapViewOfFile (hMapping, FILE_MAP_WRITE, 0, 0, 0);
#endif
if (!MapPoint)
throw std::runtime_error ("Mappoint not read");
}
/*******************
Mapper_t::~Mapper_t
*******************/
Mapper_t::~Mapper_t()
{
Close();
}
/***************
Mapper_t::Close
***************/
void Mapper_t::Close()
{
// Can be called multiple times.
// Calls to GetChunk are invalid after a call to Close.
if (MapPoint) {
UnmapViewOfFile (MapPoint);
MapPoint = NULL;
}
if (hMapping != NULL) {
CloseHandle (hMapping);
hMapping = NULL;
}
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle (hFile);
hFile = INVALID_HANDLE_VALUE;
}
}
/******************
Mapper_t::GetChunk
******************/
const char *Mapper_t::GetChunk (unsigned start)
{
return MapPoint + start;
}
#endif // OS_WINDOWS
@@ -0,0 +1,59 @@
/*****************************************************************************
$Id: mapper.h 4529 2007-07-04 11:32:22Z francis $
File: mapper.h
Date: 02Jul07
Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved.
Gmail: garbagecat10
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __Mapper__H_
#define __Mapper__H_
/**************
class Mapper_t
**************/
class Mapper_t
{
public:
Mapper_t (const std::string&);
virtual ~Mapper_t();
const char *GetChunk (unsigned);
void Close();
size_t GetFileSize() {return FileSize;}
private:
size_t FileSize;
#ifdef OS_UNIX
private:
int Fd;
const char *MapPoint;
#endif // OS_UNIX
#ifdef OS_WIN32
private:
HANDLE hFile;
HANDLE hMapping;
char *MapPoint;
#endif // OS_WIN32
};
#endif // __Mapper__H_
@@ -0,0 +1,126 @@
/*****************************************************************************
$Id: rubymain.cpp 4529 2007-07-04 11:32:22Z francis $
File: rubymain.cpp
Date: 02Jul07
Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved.
Gmail: garbagecat10
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include <iostream>
#include <stdexcept>
#include <ruby.h>
#include "mapper.h"
static VALUE EmModule;
static VALUE FastFileReader;
static VALUE Mapper;
/*********
mapper_dt
*********/
static void mapper_dt (void *ptr)
{
if (ptr)
delete (Mapper_t*) ptr;
}
/**********
mapper_new
**********/
static VALUE mapper_new (VALUE self, VALUE filename)
{
Mapper_t *m = new Mapper_t (StringValueCStr (filename));
if (!m)
rb_raise (rb_eStandardError, "No Mapper Object");
VALUE v = Data_Wrap_Struct (Mapper, 0, mapper_dt, (void*)m);
return v;
}
/****************
mapper_get_chunk
****************/
static VALUE mapper_get_chunk (VALUE self, VALUE start, VALUE length)
{
Mapper_t *m = NULL;
Data_Get_Struct (self, Mapper_t, m);
if (!m)
rb_raise (rb_eStandardError, "No Mapper Object");
// TODO, what if some moron sends us a negative start value?
unsigned _start = NUM2INT (start);
unsigned _length = NUM2INT (length);
if ((_start + _length) > m->GetFileSize())
rb_raise (rb_eStandardError, "Mapper Range Error");
const char *chunk = m->GetChunk (_start);
if (!chunk)
rb_raise (rb_eStandardError, "No Mapper Chunk");
return rb_str_new (chunk, _length);
}
/************
mapper_close
************/
static VALUE mapper_close (VALUE self)
{
Mapper_t *m = NULL;
Data_Get_Struct (self, Mapper_t, m);
if (!m)
rb_raise (rb_eStandardError, "No Mapper Object");
m->Close();
return Qnil;
}
/***********
mapper_size
***********/
static VALUE mapper_size (VALUE self)
{
Mapper_t *m = NULL;
Data_Get_Struct (self, Mapper_t, m);
if (!m)
rb_raise (rb_eStandardError, "No Mapper Object");
return INT2NUM (m->GetFileSize());
}
/**********************
Init_fastfilereaderext
**********************/
extern "C" void Init_fastfilereaderext()
{
EmModule = rb_define_module ("EventMachine");
FastFileReader = rb_define_class_under (EmModule, "FastFileReader", rb_cObject);
Mapper = rb_define_class_under (FastFileReader, "Mapper", rb_cObject);
rb_define_module_function (Mapper, "new", (VALUE(*)(...))mapper_new, 1);
rb_define_method (Mapper, "size", (VALUE(*)(...))mapper_size, 0);
rb_define_method (Mapper, "close", (VALUE(*)(...))mapper_close, 0);
rb_define_method (Mapper, "get_chunk", (VALUE(*)(...))mapper_get_chunk, 2);
}
+79
View File
@@ -0,0 +1,79 @@
/*****************************************************************************
$Id$
File: kb.cpp
Date: 24Aug07
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include "project.h"
/**************************************
KeyboardDescriptor::KeyboardDescriptor
**************************************/
KeyboardDescriptor::KeyboardDescriptor (EventMachine_t *parent_em):
EventableDescriptor (0, parent_em),
bReadAttemptedAfterClose (false)
{
#ifdef HAVE_EPOLL
EpollEvent.events = EPOLLIN;
#endif
#ifdef HAVE_KQUEUE
MyEventMachine->ArmKqueueReader (this);
#endif
}
/***************************************
KeyboardDescriptor::~KeyboardDescriptor
***************************************/
KeyboardDescriptor::~KeyboardDescriptor()
{
}
/*************************
KeyboardDescriptor::Write
*************************/
void KeyboardDescriptor::Write()
{
// Why are we here?
throw std::runtime_error ("bad code path in keyboard handler");
}
/*****************************
KeyboardDescriptor::Heartbeat
*****************************/
void KeyboardDescriptor::Heartbeat()
{
// no-op
}
/************************
KeyboardDescriptor::Read
************************/
void KeyboardDescriptor::Read()
{
char c;
(void)read (GetSocket(), &c, 1);
_GenericInboundDispatch(&c, 1);
}
+107
View File
@@ -0,0 +1,107 @@
/*****************************************************************************
$Id$
File: page.cpp
Date: 30Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include "project.h"
/******************
PageList::PageList
******************/
PageList::PageList()
{
}
/*******************
PageList::~PageList
*******************/
PageList::~PageList()
{
while (HasPages())
PopFront();
}
/***************
PageList::Front
***************/
void PageList::Front (const char **page, int *length)
{
assert (page && length);
if (HasPages()) {
Page p = Pages.front();
*page = p.Buffer;
*length = p.Size;
}
else {
*page = NULL;
*length = 0;
}
}
/******************
PageList::PopFront
******************/
void PageList::PopFront()
{
if (HasPages()) {
Page p = Pages.front();
Pages.pop_front();
if (p.Buffer)
free ((void*)p.Buffer);
}
}
/******************
PageList::HasPages
******************/
bool PageList::HasPages()
{
return (Pages.size() > 0) ? true : false;
}
/**************
PageList::Push
**************/
void PageList::Push (const char *buf, int size)
{
if (buf && (size > 0)) {
char *copy = (char*) malloc (size);
if (!copy)
throw std::runtime_error ("no memory in pagelist");
memcpy (copy, buf, size);
Pages.push_back (Page (copy, size));
}
}
+51
View File
@@ -0,0 +1,51 @@
/*****************************************************************************
$Id$
File: page.h
Date: 30Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __PageManager__H_
#define __PageManager__H_
/**************
class PageList
**************/
class PageList
{
struct Page {
Page (const char *b, size_t s): Buffer(b), Size(s) {}
const char *Buffer;
size_t Size;
};
public:
PageList();
virtual ~PageList();
void Push (const char*, int);
bool HasPages();
void Front (const char**, int*);
void PopFront();
private:
std::deque<Page> Pages;
};
#endif // __PageManager__H_
+354
View File
@@ -0,0 +1,354 @@
/*****************************************************************************
$Id$
File: pipe.cpp
Date: 30May07
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#include "project.h"
#ifdef OS_UNIX
// THIS ENTIRE FILE IS ONLY COMPILED ON UNIX-LIKE SYSTEMS.
/******************************
PipeDescriptor::PipeDescriptor
******************************/
PipeDescriptor::PipeDescriptor (int fd, pid_t subpid, EventMachine_t *parent_em):
EventableDescriptor (fd, parent_em),
bReadAttemptedAfterClose (false),
OutboundDataSize (0),
SubprocessPid (subpid)
{
#ifdef HAVE_EPOLL
EpollEvent.events = EPOLLIN;
#endif
#ifdef HAVE_KQUEUE
MyEventMachine->ArmKqueueReader (this);
#endif
}
/*******************************
PipeDescriptor::~PipeDescriptor
*******************************/
PipeDescriptor::~PipeDescriptor() NO_EXCEPT_FALSE
{
// Run down any stranded outbound data.
for (size_t i=0; i < OutboundPages.size(); i++)
OutboundPages[i].Free();
/* As a virtual destructor, we come here before the base-class
* destructor that closes our file-descriptor.
* We have to make sure the subprocess goes down (if it's not
* already down) and we have to reap the zombie.
*
* This implementation is PROVISIONAL and will surely be improved.
* The intention here is that we never block, hence the highly
* undesirable sleeps. But if we can't reap the subprocess even
* after sending it SIGKILL, then something is wrong and we
* throw a fatal exception, which is also not something we should
* be doing.
*
* Eventually the right thing to do will be to have the reactor
* core respond to SIGCHLD by chaining a handler on top of the
* one Ruby may have installed, and dealing with a list of dead
* children that are pending cleanup.
*
* Since we want to have a signal processor integrated into the
* client-visible API, let's wait until that is done before cleaning
* this up.
*
* Added a very ugly hack to support passing the subprocess's exit
* status to the user. It only makes logical sense for user code to access
* the subprocess exit status in the unbind callback. But unbind is called
* back during the EventableDescriptor destructor. So by that time there's
* no way to call back this object through an object binding, because it's
* already been cleaned up. We might have added a parameter to the unbind
* callback, but that would probably break a huge amount of existing code.
* So the hack-solution is to define an instance variable in the EventMachine
* object and stick the exit status in there, where it can easily be accessed
* with an accessor visible to user code.
* User code should ONLY access the exit status from within the unbind callback.
* Otherwise there's no guarantee it'll be valid.
* This hack won't make it impossible to run multiple EventMachines in a single
* process, but it will make it impossible to reliably nest unbind calls
* within other unbind calls. (Not sure if that's even possible.)
*/
assert (MyEventMachine);
/* Another hack to make the SubprocessPid available to get_subprocess_status */
MyEventMachine->SubprocessPid = SubprocessPid;
/* 01Mar09: Updated to use a small nanosleep in a loop. When nanosleep is interrupted by SIGCHLD,
* it resumes the system call after processing the signal (resulting in unnecessary latency).
* Calling nanosleep in a loop avoids this problem.
*/
struct timespec req = {0, 50000000}; // 0.05s
int n;
// wait 0.5s for the process to die
for (n=0; n<10; n++) {
if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return;
nanosleep (&req, NULL);
}
// send SIGTERM and wait another 1s
kill (SubprocessPid, SIGTERM);
for (n=0; n<20; n++) {
nanosleep (&req, NULL);
if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return;
}
// send SIGKILL and wait another 5s
kill (SubprocessPid, SIGKILL);
for (n=0; n<100; n++) {
nanosleep (&req, NULL);
if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return;
}
// still not dead, give up!
throw std::runtime_error ("unable to reap subprocess");
}
/********************
PipeDescriptor::Read
********************/
void PipeDescriptor::Read()
{
int sd = GetSocket();
if (sd == INVALID_SOCKET) {
assert (!bReadAttemptedAfterClose);
bReadAttemptedAfterClose = true;
return;
}
LastActivity = MyEventMachine->GetCurrentLoopTime();
int total_bytes_read = 0;
char readbuffer [16 * 1024];
for (int i=0; i < 10; i++) {
// Don't read just one buffer and then move on. This is faster
// if there is a lot of incoming.
// But don't read indefinitely. Give other sockets a chance to run.
// NOTICE, we're reading one less than the buffer size.
// That's so we can put a guard byte at the end of what we send
// to user code.
// Use read instead of recv, which on Linux gives a "socket operation
// on nonsocket" error.
int r = read (sd, readbuffer, sizeof(readbuffer) - 1);
//cerr << "<R:" << r << ">";
if (r > 0) {
total_bytes_read += r;
// Add a null-terminator at the the end of the buffer
// that we will send to the callback.
// DO NOT EVER CHANGE THIS. We want to explicitly allow users
// to be able to depend on this behavior, so they will have
// the option to do some things faster. Additionally it's
// a security guard against buffer overflows.
readbuffer [r] = 0;
_GenericInboundDispatch(readbuffer, r);
}
else if (r == 0) {
break;
}
else {
// Basically a would-block, meaning we've read everything there is to read.
break;
}
}
if (total_bytes_read == 0) {
// If we read no data on a socket that selected readable,
// it generally means the other end closed the connection gracefully.
ScheduleClose (false);
//bCloseNow = true;
}
}
/*********************
PipeDescriptor::Write
*********************/
void PipeDescriptor::Write()
{
int sd = GetSocket();
assert (sd != INVALID_SOCKET);
LastActivity = MyEventMachine->GetCurrentLoopTime();
char output_buffer [16 * 1024];
size_t nbytes = 0;
while ((OutboundPages.size() > 0) && (nbytes < sizeof(output_buffer))) {
OutboundPage *op = &(OutboundPages[0]);
if ((nbytes + op->Length - op->Offset) < sizeof (output_buffer)) {
memcpy (output_buffer + nbytes, op->Buffer + op->Offset, op->Length - op->Offset);
nbytes += (op->Length - op->Offset);
op->Free();
OutboundPages.pop_front();
}
else {
int len = sizeof(output_buffer) - nbytes;
memcpy (output_buffer + nbytes, op->Buffer + op->Offset, len);
op->Offset += len;
nbytes += len;
}
}
// We should never have gotten here if there were no data to write,
// so assert that as a sanity check.
// Don't bother to make sure nbytes is less than output_buffer because
// if it were we probably would have crashed already.
assert (nbytes > 0);
assert (GetSocket() != INVALID_SOCKET);
int bytes_written = write (GetSocket(), output_buffer, nbytes);
#ifdef OS_WIN32
int e = WSAGetLastError();
#else
int e = errno;
#endif
if (bytes_written > 0) {
OutboundDataSize -= bytes_written;
if ((size_t)bytes_written < nbytes) {
int len = nbytes - bytes_written;
char *buffer = (char*) malloc (len + 1);
if (!buffer)
throw std::runtime_error ("bad alloc throwing back data");
memcpy (buffer, output_buffer + bytes_written, len);
buffer [len] = 0;
OutboundPages.push_front (OutboundPage (buffer, len));
}
#ifdef HAVE_EPOLL
EpollEvent.events = EPOLLIN;
if (SelectForWrite())
EpollEvent.events |= EPOLLOUT;
assert (MyEventMachine);
MyEventMachine->Modify (this);
#endif
}
else {
#ifdef OS_UNIX
if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EINTR))
#endif
#ifdef OS_WIN32
if ((e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK))
#endif
Close();
}
}
/*************************
PipeDescriptor::Heartbeat
*************************/
void PipeDescriptor::Heartbeat()
{
// If an inactivity timeout is defined, then check for it.
if (InactivityTimeout && ((MyEventMachine->GetCurrentLoopTime() - LastActivity) >= InactivityTimeout))
ScheduleClose (false);
//bCloseNow = true;
}
/*****************************
PipeDescriptor::SelectForRead
*****************************/
bool PipeDescriptor::SelectForRead()
{
/* Pipe descriptors, being local by definition, don't have
* a pending state, so this is simpler than for the
* ConnectionDescriptor object.
*/
return bPaused ? false : true;
}
/******************************
PipeDescriptor::SelectForWrite
******************************/
bool PipeDescriptor::SelectForWrite()
{
/* Pipe descriptors, being local by definition, don't have
* a pending state, so this is simpler than for the
* ConnectionDescriptor object.
*/
return (GetOutboundDataSize() > 0) && !bPaused ? true : false;
}
/********************************
PipeDescriptor::SendOutboundData
********************************/
int PipeDescriptor::SendOutboundData (const char *data, unsigned long length)
{
//if (bCloseNow || bCloseAfterWriting)
if (IsCloseScheduled())
return 0;
if (!data && (length > 0))
throw std::runtime_error ("bad outbound data");
char *buffer = (char *) malloc (length + 1);
if (!buffer)
throw std::runtime_error ("no allocation for outbound data");
memcpy (buffer, data, length);
buffer [length] = 0;
OutboundPages.push_back (OutboundPage (buffer, length));
OutboundDataSize += length;
#ifdef HAVE_EPOLL
EpollEvent.events = (EPOLLIN | EPOLLOUT);
assert (MyEventMachine);
MyEventMachine->Modify (this);
#endif
return length;
}
/********************************
PipeDescriptor::GetSubprocessPid
********************************/
bool PipeDescriptor::GetSubprocessPid (pid_t *pid)
{
bool ok = false;
if (pid && (SubprocessPid > 0)) {
*pid = SubprocessPid;
ok = true;
}
return ok;
}
#endif // OS_UNIX
+174
View File
@@ -0,0 +1,174 @@
/*****************************************************************************
$Id$
File: project.h
Date: 06Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __Project__H_
#define __Project__H_
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <deque>
#include <string>
#include <sstream>
#include <stdexcept>
#ifdef OS_UNIX
#include <signal.h>
#include <netdb.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pwd.h>
#include <string.h>
typedef int SOCKET;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#ifdef OS_SOLARIS8
#include <strings.h>
#include <sys/un.h>
#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif
// INADDR_NONE is undefined on Solaris < 8. Thanks to Brett Eisenberg and Tim Pease.
#ifndef INADDR_NONE
#define INADDR_NONE ((unsigned long)-1)
#endif
#endif /* OS_SOLARIS8 */
#ifdef _AIX
#include <strings.h>
#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif
#endif /* _AIX */
#ifdef OS_DARWIN
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif /* OS_DARWIN */
#endif /* OS_UNIX */
#ifdef OS_WIN32
// 21Sep09: windows limits select() to 64 sockets by default, we increase it to 1024 here (before including winsock2.h)
// 18Jun12: fd_setsize must be changed in the ruby binary (not in this extension). redefining it also causes segvs, see eventmachine/eventmachine#333
//#define FD_SETSIZE 1024
// WIN32_LEAN_AND_MEAN excludes APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <rpc.h>
#include <fcntl.h>
#include <assert.h>
// Older versions of MinGW in the Ruby Dev Kit do not provide the getaddrinfo hint flags
#ifndef AI_ADDRCONFIG
#define AI_ADDRCONFIG 0x0400
#endif
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0x0008
#endif
// Use the Win32 wrapper library that Ruby owns to be able to close sockets with the close() function
#define RUBY_EXPORT
#include <ruby/defines.h>
#include <ruby/win32.h>
#endif /* OS_WIN32 */
#if !defined(_MSC_VER) || _MSC_VER > 1500
#include <stdint.h>
#endif
#ifdef WITH_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
#ifdef HAVE_EPOLL
#include <sys/epoll.h>
#endif
#ifdef HAVE_KQUEUE
#include <sys/event.h>
#include <sys/queue.h>
#endif
#ifdef HAVE_INOTIFY
#include <sys/inotify.h>
#endif
#ifdef HAVE_OLD_INOTIFY
#include <sys/syscall.h>
#include <linux/inotify.h>
static inline int inotify_init (void) { return syscall (__NR_inotify_init); }
static inline int inotify_add_watch (int fd, const char *name, __u32 mask) { return syscall (__NR_inotify_add_watch, fd, name, mask); }
static inline int inotify_rm_watch (int fd, __u32 wd) { return syscall (__NR_inotify_rm_watch, fd, wd); }
#define HAVE_INOTIFY 1
#endif
#ifdef HAVE_INOTIFY
#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event))
#endif
#ifdef HAVE_WRITEV
#include <sys/uio.h>
#endif
#if __cplusplus
extern "C" {
#endif
typedef void (*EMCallback)(const unsigned long, int, const char*, const unsigned long);
#if __cplusplus
}
#endif
#if defined(__GNUC__) && (__GNUC__ >= 3)
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif
#include "binder.h"
#include "em.h"
#include "ed.h"
#include "page.h"
#include "ssl.h"
#include "eventmachine.h"
#endif // __Project__H_
File diff suppressed because it is too large Load Diff
+619
View File
@@ -0,0 +1,619 @@
/*****************************************************************************
$Id$
File: ssl.cpp
Date: 30Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifdef WITH_SSL
#include "project.h"
bool SslContext_t::bLibraryInitialized = false;
static void InitializeDefaultCredentials();
static EVP_PKEY *DefaultPrivateKey = NULL;
static X509 *DefaultCertificate = NULL;
static char PrivateMaterials[] = {
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV\n"
"Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/\n"
"AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB\n"
"AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk\n"
"H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D\n"
"I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo\n"
"6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg\n"
"w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK\n"
"PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ\n"
"xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k\n"
"xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa\n"
"dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn\n"
"2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=\n"
"-----END RSA PRIVATE KEY-----\n"
"-----BEGIN CERTIFICATE-----\n"
"MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
"VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw\n"
"FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG\n"
"A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu\n"
"ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw\n"
"NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH\n"
"EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n\n"
"aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI\n"
"hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB\n"
"AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw\n"
"VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3\n"
"9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID\n"
"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV\n"
"HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG\n"
"EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD\n"
"VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE\n"
"AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy\n"
"aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG\n"
"SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0\n"
"Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j\n"
"uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy\n"
"-----END CERTIFICATE-----\n"};
/* These private materials were made with:
* openssl req -new -x509 -keyout cakey.pem -out cacert.pem -nodes -days 6500
* TODO: We need a full-blown capability to work with user-supplied
* keypairs and properly-signed certificates.
*/
/*****************
builtin_passwd_cb
*****************/
extern "C" int builtin_passwd_cb (char *buf UNUSED, int bufsize UNUSED, int rwflag UNUSED, void *userdata UNUSED)
{
strcpy (buf, "kittycat");
return 8;
}
/****************************
InitializeDefaultCredentials
****************************/
static void InitializeDefaultCredentials()
{
BIO *bio = BIO_new_mem_buf (PrivateMaterials, -1);
assert (bio);
if (DefaultPrivateKey) {
// we may come here in a restart.
EVP_PKEY_free (DefaultPrivateKey);
DefaultPrivateKey = NULL;
}
PEM_read_bio_PrivateKey (bio, &DefaultPrivateKey, builtin_passwd_cb, 0);
if (DefaultCertificate) {
// we may come here in a restart.
X509_free (DefaultCertificate);
DefaultCertificate = NULL;
}
PEM_read_bio_X509 (bio, &DefaultCertificate, NULL, 0);
BIO_free (bio);
}
/**************************
SslContext_t::SslContext_t
**************************/
SslContext_t::SslContext_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version) :
bIsServer (is_server),
pCtx (NULL),
PrivateKey (NULL),
Certificate (NULL)
{
/* TODO: the usage of the specified private-key and cert-chain filenames only applies to
* client-side connections at this point. Server connections currently use the default materials.
* That needs to be fixed asap.
* Also, in this implementation, server-side connections use statically defined X-509 defaults.
* One thing I'm really not clear on is whether or not you have to explicitly free X509 and EVP_PKEY
* objects when we call our destructor, or whether just calling SSL_CTX_free is enough.
*/
if (!bLibraryInitialized) {
bLibraryInitialized = true;
SSL_library_init();
OpenSSL_add_ssl_algorithms();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_crypto_strings();
InitializeDefaultCredentials();
}
pCtx = SSL_CTX_new (bIsServer ? SSLv23_server_method() : SSLv23_client_method());
if (!pCtx)
throw std::runtime_error ("no SSL context");
SSL_CTX_set_options (pCtx, SSL_OP_ALL);
#ifdef SSL_CTRL_CLEAR_OPTIONS
SSL_CTX_clear_options (pCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
# ifdef SSL_OP_NO_TLSv1_1
SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_1);
# endif
# ifdef SSL_OP_NO_TLSv1_2
SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_2);
# endif
#endif
if (!(ssl_version & EM_PROTO_SSLv2))
SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv2);
if (!(ssl_version & EM_PROTO_SSLv3))
SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv3);
if (!(ssl_version & EM_PROTO_TLSv1))
SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1);
#ifdef SSL_OP_NO_TLSv1_1
if (!(ssl_version & EM_PROTO_TLSv1_1))
SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_1);
#endif
#ifdef SSL_OP_NO_TLSv1_2
if (!(ssl_version & EM_PROTO_TLSv1_2))
SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode (pCtx, SSL_MODE_RELEASE_BUFFERS);
#endif
if (bIsServer) {
// The SSL_CTX calls here do NOT allocate memory.
int e;
if (privkeyfile.length() > 0)
e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM);
else
e = SSL_CTX_use_PrivateKey (pCtx, DefaultPrivateKey);
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);
if (certchainfile.length() > 0)
e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str());
else
e = SSL_CTX_use_certificate (pCtx, DefaultCertificate);
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);
if (dhparam.length() > 0) {
DH *dh;
BIO *bio;
bio = BIO_new_file(dhparam.c_str(), "r");
if (bio == NULL) {
char buf [500];
snprintf (buf, sizeof(buf)-1, "dhparam: BIO_new_file(%s) failed", dhparam.c_str());
throw std::runtime_error (buf);
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
if (dh == NULL) {
BIO_free(bio);
char buf [500];
snprintf (buf, sizeof(buf)-1, "dhparam: PEM_read_bio_DHparams(%s) failed", dhparam.c_str());
throw std::runtime_error (buf);
}
SSL_CTX_set_tmp_dh(pCtx, dh);
DH_free(dh);
BIO_free(bio);
}
if (ecdh_curve.length() > 0) {
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
int nid;
EC_KEY *ecdh;
nid = OBJ_sn2nid((const char *) ecdh_curve.c_str());
if (nid == 0) {
char buf [200];
snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unknown curve name: %s", ecdh_curve.c_str());
throw std::runtime_error (buf);
}
ecdh = EC_KEY_new_by_curve_name(nid);
if (ecdh == NULL) {
char buf [200];
snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unable to create: %s", ecdh_curve.c_str());
throw std::runtime_error (buf);
}
SSL_CTX_set_options(pCtx, SSL_OP_SINGLE_ECDH_USE);
SSL_CTX_set_tmp_ecdh(pCtx, ecdh);
EC_KEY_free(ecdh);
#else
throw std::runtime_error ("No openssl ECDH support");
#endif
}
}
if (cipherlist.length() > 0)
SSL_CTX_set_cipher_list (pCtx, cipherlist.c_str());
else
SSL_CTX_set_cipher_list (pCtx, "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH");
if (bIsServer) {
SSL_CTX_sess_set_cache_size (pCtx, 128);
SSL_CTX_set_session_id_context (pCtx, (unsigned char*)"eventmachine", 12);
}
else {
int e;
if (privkeyfile.length() > 0) {
e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM);
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);
}
if (certchainfile.length() > 0) {
e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str());
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);
}
}
}
/***************************
SslContext_t::~SslContext_t
***************************/
SslContext_t::~SslContext_t()
{
if (pCtx)
SSL_CTX_free (pCtx);
if (PrivateKey)
EVP_PKEY_free (PrivateKey);
if (Certificate)
X509_free (Certificate);
}
/******************
SslBox_t::SslBox_t
******************/
SslBox_t::SslBox_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const std::string &snihostname, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version, const uintptr_t binding):
bIsServer (is_server),
bHandshakeCompleted (false),
bVerifyPeer (verify_peer),
bFailIfNoPeerCert (fail_if_no_peer_cert),
pSSL (NULL),
pbioRead (NULL),
pbioWrite (NULL)
{
/* TODO someday: make it possible to re-use SSL contexts so we don't have to create
* a new one every time we come here.
*/
Context = new SslContext_t (bIsServer, privkeyfile, certchainfile, cipherlist, ecdh_curve, dhparam, ssl_version);
assert (Context);
pbioRead = BIO_new (BIO_s_mem());
assert (pbioRead);
pbioWrite = BIO_new (BIO_s_mem());
assert (pbioWrite);
pSSL = SSL_new (Context->pCtx);
assert (pSSL);
if (snihostname.length() > 0) {
SSL_set_tlsext_host_name (pSSL, snihostname.c_str());
}
SSL_set_bio (pSSL, pbioRead, pbioWrite);
// Store a pointer to the binding signature in the SSL object so we can retrieve it later
SSL_set_ex_data(pSSL, 0, (void*) binding);
if (bVerifyPeer) {
int mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
if (bFailIfNoPeerCert)
mode = mode | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
SSL_set_verify(pSSL, mode, ssl_verify_wrapper);
}
if (!bIsServer) {
int e = SSL_connect (pSSL);
if (e != 1)
ERR_print_errors_fp(stderr);
}
}
/*******************
SslBox_t::~SslBox_t
*******************/
SslBox_t::~SslBox_t()
{
// Freeing pSSL will also free the associated BIOs, so DON'T free them separately.
if (pSSL) {
if (SSL_get_shutdown (pSSL) & SSL_RECEIVED_SHUTDOWN)
SSL_shutdown (pSSL);
else
SSL_clear (pSSL);
SSL_free (pSSL);
}
delete Context;
}
/***********************
SslBox_t::PutCiphertext
***********************/
bool SslBox_t::PutCiphertext (const char *buf, int bufsize)
{
assert (buf && (bufsize > 0));
assert (pbioRead);
int n = BIO_write (pbioRead, buf, bufsize);
return (n == bufsize) ? true : false;
}
/**********************
SslBox_t::GetPlaintext
**********************/
int SslBox_t::GetPlaintext (char *buf, int bufsize)
{
if (!SSL_is_init_finished (pSSL)) {
int e = bIsServer ? SSL_accept (pSSL) : SSL_connect (pSSL);
if (e != 1) {
int er = SSL_get_error (pSSL, e);
if (er != SSL_ERROR_WANT_READ) {
ERR_print_errors_fp(stderr);
// Return -1 for a nonfatal error, -2 for an error that should force the connection down.
return (er == SSL_ERROR_SSL) ? (-2) : (-1);
}
else
return 0;
}
bHandshakeCompleted = true;
// If handshake finished, FALL THROUGH and return the available plaintext.
}
if (!SSL_is_init_finished (pSSL)) {
// We can get here if a browser abandons a handshake.
// The user can see a warning dialog and abort the connection.
//cerr << "<SSL_incomp>";
return 0;
}
//cerr << "CIPH: " << SSL_get_cipher (pSSL) << endl;
int n = SSL_read (pSSL, buf, bufsize);
if (n >= 0) {
return n;
}
else {
if (SSL_get_error (pSSL, n) == SSL_ERROR_WANT_READ) {
return 0;
}
else {
return -1;
}
}
return 0;
}
/**************************
SslBox_t::CanGetCiphertext
**************************/
bool SslBox_t::CanGetCiphertext()
{
assert (pbioWrite);
return BIO_pending (pbioWrite) ? true : false;
}
/***********************
SslBox_t::GetCiphertext
***********************/
int SslBox_t::GetCiphertext (char *buf, int bufsize)
{
assert (pbioWrite);
assert (buf && (bufsize > 0));
return BIO_read (pbioWrite, buf, bufsize);
}
/**********************
SslBox_t::PutPlaintext
**********************/
int SslBox_t::PutPlaintext (const char *buf, int bufsize)
{
// The caller will interpret the return value as the number of bytes written.
// WARNING WARNING WARNING, are there any situations in which a 0 or -1 return
// from SSL_write means we should immediately retry? The socket-machine loop
// will probably wait for a time-out cycle (perhaps a second) before re-trying.
// THIS WOULD CAUSE A PERCEPTIBLE DELAY!
/* We internally queue any outbound plaintext that can't be dispatched
* because we're in the middle of a handshake or something.
* When we get called, try to send any queued data first, and then
* send the caller's data (or queue it). We may get called with no outbound
* data, which means we try to send the outbound queue and that's all.
*
* Return >0 if we wrote any data, 0 if we didn't, and <0 for a fatal error.
* Note that if we return 0, the connection is still considered live
* and we are signalling that we have accepted the outbound data (if any).
*/
OutboundQ.Push (buf, bufsize);
if (!SSL_is_init_finished (pSSL))
return 0;
bool fatal = false;
bool did_work = false;
int pending = BIO_pending(pbioWrite);
while (OutboundQ.HasPages() && pending < SSLBOX_WRITE_BUFFER_SIZE) {
const char *page;
int length;
OutboundQ.Front (&page, &length);
assert (page && (length > 0));
int n = SSL_write (pSSL, page, length);
pending = BIO_pending(pbioWrite);
if (n > 0) {
did_work = true;
OutboundQ.PopFront();
}
else {
int er = SSL_get_error (pSSL, n);
if ((er != SSL_ERROR_WANT_READ) && (er != SSL_ERROR_WANT_WRITE))
fatal = true;
break;
}
}
if (did_work)
return 1;
else if (fatal)
return -1;
else
return 0;
}
/**********************
SslBox_t::GetPeerCert
**********************/
X509 *SslBox_t::GetPeerCert()
{
X509 *cert = NULL;
if (pSSL)
cert = SSL_get_peer_certificate(pSSL);
return cert;
}
/**********************
SslBox_t::GetCipherBits
**********************/
int SslBox_t::GetCipherBits()
{
int bits = -1;
if (pSSL)
SSL_get_cipher_bits(pSSL, &bits);
return bits;
}
/**********************
SslBox_t::GetCipherName
**********************/
const char *SslBox_t::GetCipherName()
{
if (pSSL)
return SSL_get_cipher_name(pSSL);
return NULL;
}
/**********************
SslBox_t::GetCipherProtocol
**********************/
const char *SslBox_t::GetCipherProtocol()
{
if (pSSL)
return SSL_get_cipher_version(pSSL);
return NULL;
}
/**********************
SslBox_t::GetSNIHostname
**********************/
const char *SslBox_t::GetSNIHostname()
{
#ifdef TLSEXT_NAMETYPE_host_name
if (pSSL)
return SSL_get_servername (pSSL, TLSEXT_NAMETYPE_host_name);
#endif
return NULL;
}
/******************
ssl_verify_wrapper
*******************/
extern "C" int ssl_verify_wrapper(int preverify_ok UNUSED, X509_STORE_CTX *ctx)
{
uintptr_t binding;
X509 *cert;
SSL *ssl;
BUF_MEM *buf;
BIO *out;
int result;
cert = X509_STORE_CTX_get_current_cert(ctx);
ssl = (SSL*) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
binding = (uintptr_t) SSL_get_ex_data(ssl, 0);
out = BIO_new(BIO_s_mem());
PEM_write_bio_X509(out, cert);
BIO_write(out, "\0", 1);
BIO_get_mem_ptr(out, &buf);
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject(binding));
result = (cd->VerifySslPeer(buf->data) == true ? 1 : 0);
BIO_free(out);
return result;
}
#endif // WITH_SSL
+103
View File
@@ -0,0 +1,103 @@
/*****************************************************************************
$Id$
File: ssl.h
Date: 30Apr06
Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd
This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) the GNU General Public License
as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version; or 2) Ruby's License.
See the file COPYING for complete licensing information.
*****************************************************************************/
#ifndef __SslBox__H_
#define __SslBox__H_
#ifdef WITH_SSL
/******************
class SslContext_t
******************/
class SslContext_t
{
public:
SslContext_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version);
virtual ~SslContext_t();
private:
static bool bLibraryInitialized;
private:
bool bIsServer;
SSL_CTX *pCtx;
EVP_PKEY *PrivateKey;
X509 *Certificate;
friend class SslBox_t;
};
/**************
class SslBox_t
**************/
#define SSLBOX_INPUT_CHUNKSIZE 2019
#define SSLBOX_OUTPUT_CHUNKSIZE 2048
#define SSLBOX_WRITE_BUFFER_SIZE 8192 // (SSLBOX_OUTPUT_CHUNKSIZE * 4)
class SslBox_t
{
public:
SslBox_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const std::string &snihostname, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version, const uintptr_t binding);
virtual ~SslBox_t();
int PutPlaintext (const char*, int);
int GetPlaintext (char*, int);
bool PutCiphertext (const char*, int);
bool CanGetCiphertext();
int GetCiphertext (char*, int);
bool IsHandshakeCompleted() {return bHandshakeCompleted;}
X509 *GetPeerCert();
int GetCipherBits();
const char *GetCipherName();
const char *GetCipherProtocol();
const char *GetSNIHostname();
void Shutdown();
protected:
SslContext_t *Context;
bool bIsServer;
bool bHandshakeCompleted;
bool bVerifyPeer;
bool bFailIfNoPeerCert;
SSL *pSSL;
BIO *pbioRead;
BIO *pbioWrite;
PageList OutboundQ;
};
extern "C" int ssl_verify_wrapper(int, X509_STORE_CTX*);
#endif // WITH_SSL
#endif // __SslBox__H_
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry excluding="src/" kind="src" path=""/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="src"/>
</classpath>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>em_reactor</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,613 @@
/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage:: http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/
package com.rubyeventmachine;
import java.io.*;
import java.nio.channels.*;
import java.util.*;
import java.nio.*;
import java.net.*;
import java.util.concurrent.atomic.*;
import java.security.*;
public class EmReactor {
public final int EM_TIMER_FIRED = 100;
public final int EM_CONNECTION_READ = 101;
public final int EM_CONNECTION_UNBOUND = 102;
public final int EM_CONNECTION_ACCEPTED = 103;
public final int EM_CONNECTION_COMPLETED = 104;
public final int EM_LOOPBREAK_SIGNAL = 105;
public final int EM_CONNECTION_NOTIFY_READABLE = 106;
public final int EM_CONNECTION_NOTIFY_WRITABLE = 107;
public final int EM_SSL_HANDSHAKE_COMPLETED = 108;
public final int EM_SSL_VERIFY = 109;
public final int EM_PROXY_TARGET_UNBOUND = 110;
public final int EM_PROXY_COMPLETED = 111;
public final int EM_PROTO_SSLv2 = 2;
public final int EM_PROTO_SSLv3 = 4;
public final int EM_PROTO_TLSv1 = 8;
public final int EM_PROTO_TLSv1_1 = 16;
public final int EM_PROTO_TLSv1_2 = 32;
private Selector mySelector;
private TreeMap<Long, ArrayList<Long>> Timers;
private HashMap<Long, EventableChannel> Connections;
private HashMap<Long, ServerSocketChannel> Acceptors;
private ArrayList<Long> NewConnections;
private ArrayList<Long> UnboundConnections;
private ArrayList<EventableSocketChannel> DetachedConnections;
private boolean bRunReactor;
private long BindingIndex;
private AtomicBoolean loopBreaker;
private ByteBuffer myReadBuffer;
private int timerQuantum;
public EmReactor() {
Timers = new TreeMap<Long, ArrayList<Long>>();
Connections = new HashMap<Long, EventableChannel>();
Acceptors = new HashMap<Long, ServerSocketChannel>();
NewConnections = new ArrayList<Long>();
UnboundConnections = new ArrayList<Long>();
DetachedConnections = new ArrayList<EventableSocketChannel>();
BindingIndex = 0;
loopBreaker = new AtomicBoolean();
loopBreaker.set(false);
myReadBuffer = ByteBuffer.allocate(32*1024); // don't use a direct buffer. Ruby doesn't seem to like them.
timerQuantum = 98;
}
/**
* This is a no-op stub, intended to be overridden in user code.
*/
public void eventCallback (long sig, int eventType, ByteBuffer data, long data2) {
System.out.println ("Default callback: "+sig+" "+eventType+" "+data+" "+data2);
}
public void eventCallback (long sig, int eventType, ByteBuffer data) {
eventCallback (sig, eventType, data, 0);
}
public void run() {
try {
mySelector = Selector.open();
bRunReactor = true;
} catch (IOException e) {
throw new RuntimeException ("Could not open selector", e);
}
while (bRunReactor) {
runLoopbreaks();
if (!bRunReactor) break;
runTimers();
if (!bRunReactor) break;
removeUnboundConnections();
checkIO();
addNewConnections();
processIO();
}
close();
}
void addNewConnections() {
ListIterator<EventableSocketChannel> iter = DetachedConnections.listIterator(0);
while (iter.hasNext()) {
EventableSocketChannel ec = iter.next();
ec.cleanup();
}
DetachedConnections.clear();
ListIterator<Long> iter2 = NewConnections.listIterator(0);
while (iter2.hasNext()) {
long b = iter2.next();
EventableChannel ec = Connections.get(b);
if (ec != null) {
try {
ec.register();
} catch (ClosedChannelException e) {
UnboundConnections.add (ec.getBinding());
}
}
}
NewConnections.clear();
}
void removeUnboundConnections() {
ListIterator<Long> iter = UnboundConnections.listIterator(0);
while (iter.hasNext()) {
long b = iter.next();
EventableChannel ec = Connections.remove(b);
if (ec != null) {
eventCallback (b, EM_CONNECTION_UNBOUND, null);
ec.close();
EventableSocketChannel sc = (EventableSocketChannel) ec;
if (sc != null && sc.isAttached())
DetachedConnections.add (sc);
}
}
UnboundConnections.clear();
}
void checkIO() {
long timeout;
if (NewConnections.size() > 0) {
timeout = -1;
} else if (!Timers.isEmpty()) {
long now = new Date().getTime();
long k = Timers.firstKey();
long diff = k-now;
if (diff <= 0)
timeout = -1; // don't wait, just poll once
else
timeout = diff;
} else {
timeout = 0; // wait indefinitely
}
try {
if (timeout == -1)
mySelector.selectNow();
else
mySelector.select(timeout);
} catch (IOException e) {
e.printStackTrace();
}
}
void processIO() {
Iterator<SelectionKey> it = mySelector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey k = it.next();
it.remove();
if (k.isConnectable())
isConnectable(k);
else if (k.isAcceptable())
isAcceptable(k);
else {
if (k.isWritable())
isWritable(k);
if (k.isReadable())
isReadable(k);
}
}
}
void isAcceptable (SelectionKey k) {
ServerSocketChannel ss = (ServerSocketChannel) k.channel();
SocketChannel sn;
long b;
for (int n = 0; n < 10; n++) {
try {
sn = ss.accept();
if (sn == null)
break;
} catch (IOException e) {
e.printStackTrace();
k.cancel();
ServerSocketChannel server = Acceptors.remove(k.attachment());
if (server != null)
try{ server.close(); } catch (IOException ex) {};
break;
}
try {
sn.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
continue;
}
b = createBinding();
EventableSocketChannel ec = new EventableSocketChannel (sn, b, mySelector);
Connections.put (b, ec);
NewConnections.add (b);
eventCallback (((Long)k.attachment()).longValue(), EM_CONNECTION_ACCEPTED, null, b);
}
}
void isReadable (SelectionKey k) {
EventableChannel ec = (EventableChannel) k.attachment();
long b = ec.getBinding();
if (ec.isWatchOnly()) {
if (ec.isNotifyReadable())
eventCallback (b, EM_CONNECTION_NOTIFY_READABLE, null);
} else {
myReadBuffer.clear();
try {
ec.readInboundData (myReadBuffer);
myReadBuffer.flip();
if (myReadBuffer.limit() > 0)
eventCallback (b, EM_CONNECTION_READ, myReadBuffer);
} catch (IOException e) {
UnboundConnections.add (b);
}
}
}
void isWritable (SelectionKey k) {
EventableChannel ec = (EventableChannel) k.attachment();
long b = ec.getBinding();
if (ec.isWatchOnly()) {
if (ec.isNotifyWritable())
eventCallback (b, EM_CONNECTION_NOTIFY_WRITABLE, null);
}
else {
try {
if (!ec.writeOutboundData())
UnboundConnections.add (b);
} catch (IOException e) {
UnboundConnections.add (b);
}
}
}
void isConnectable (SelectionKey k) {
EventableSocketChannel ec = (EventableSocketChannel) k.attachment();
long b = ec.getBinding();
try {
if (ec.finishConnecting())
eventCallback (b, EM_CONNECTION_COMPLETED, null);
else
UnboundConnections.add (b);
} catch (IOException e) {
UnboundConnections.add (b);
}
}
void close() {
try {
if (mySelector != null)
mySelector.close();
} catch (IOException e) {}
mySelector = null;
// run down open connections and sockets.
Iterator<ServerSocketChannel> i = Acceptors.values().iterator();
while (i.hasNext()) {
try {
i.next().close();
} catch (IOException e) {}
}
// 29Sep09: We create an ArrayList of the existing connections, then iterate over
// that to call unbind on them. This is because an unbind can trigger a reconnect,
// which will add to the Connections HashMap, causing a ConcurrentModificationException.
// XXX: The correct behavior here would be to latch the various reactor methods to return
// immediately if the reactor is shutting down.
ArrayList<EventableChannel> conns = new ArrayList<EventableChannel>();
Iterator<EventableChannel> i2 = Connections.values().iterator();
while (i2.hasNext()) {
EventableChannel ec = i2.next();
if (ec != null) {
conns.add (ec);
}
}
Connections.clear();
ListIterator<EventableChannel> i3 = conns.listIterator(0);
while (i3.hasNext()) {
EventableChannel ec = i3.next();
eventCallback (ec.getBinding(), EM_CONNECTION_UNBOUND, null);
ec.close();
EventableSocketChannel sc = (EventableSocketChannel) ec;
if (sc != null && sc.isAttached())
DetachedConnections.add (sc);
}
ListIterator<EventableSocketChannel> i4 = DetachedConnections.listIterator(0);
while (i4.hasNext()) {
EventableSocketChannel ec = i4.next();
ec.cleanup();
}
DetachedConnections.clear();
}
void runLoopbreaks() {
if (loopBreaker.getAndSet(false)) {
eventCallback (0, EM_LOOPBREAK_SIGNAL, null);
}
}
public void stop() {
bRunReactor = false;
signalLoopbreak();
}
void runTimers() {
long now = new Date().getTime();
while (!Timers.isEmpty()) {
long k = Timers.firstKey();
if (k > now)
break;
ArrayList<Long> callbacks = Timers.get(k);
Timers.remove(k);
// Fire all timers at this timestamp
ListIterator<Long> iter = callbacks.listIterator(0);
while (iter.hasNext()) {
eventCallback (0, EM_TIMER_FIRED, null, iter.next().longValue());
}
}
}
public long installOneshotTimer (long milliseconds) {
long s = createBinding();
long deadline = new Date().getTime() + milliseconds;
if (Timers.containsKey(deadline)) {
Timers.get(deadline).add(s);
} else {
ArrayList<Long> callbacks = new ArrayList<Long>();
callbacks.add(s);
Timers.put(deadline, callbacks);
}
return s;
}
public long startTcpServer (SocketAddress sa) throws EmReactorException {
try {
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.socket().bind (sa);
long s = createBinding();
Acceptors.put(s, server);
server.register(mySelector, SelectionKey.OP_ACCEPT, s);
return s;
} catch (IOException e) {
throw new EmReactorException ("unable to open socket acceptor: " + e.toString());
}
}
public long startTcpServer (String address, int port) throws EmReactorException {
return startTcpServer (new InetSocketAddress (address, port));
}
public void stopTcpServer (long signature) throws IOException {
ServerSocketChannel server = Acceptors.remove(signature);
if (server != null)
server.close();
else
throw new RuntimeException ("failed to close unknown acceptor");
}
public long openUdpSocket (InetSocketAddress address) throws IOException {
// TODO, don't throw an exception out of here.
DatagramChannel dg = DatagramChannel.open();
dg.configureBlocking(false);
dg.socket().bind(address);
long b = createBinding();
EventableChannel ec = new EventableDatagramChannel (dg, b, mySelector);
dg.register(mySelector, SelectionKey.OP_READ, ec);
Connections.put(b, ec);
return b;
}
public long openUdpSocket (String address, int port) throws IOException {
return openUdpSocket (new InetSocketAddress (address, port));
}
public void sendData (long sig, ByteBuffer bb) throws IOException {
Connections.get(sig).scheduleOutboundData( bb );
}
public void sendData (long sig, byte[] data) throws IOException {
sendData (sig, ByteBuffer.wrap(data));
}
public void setCommInactivityTimeout (long sig, long mills) {
Connections.get(sig).setCommInactivityTimeout (mills);
}
public void sendDatagram (long sig, byte[] data, int length, String recipAddress, int recipPort) {
sendDatagram (sig, ByteBuffer.wrap(data), recipAddress, recipPort);
}
public void sendDatagram (long sig, ByteBuffer bb, String recipAddress, int recipPort) {
(Connections.get(sig)).scheduleOutboundDatagram( bb, recipAddress, recipPort);
}
public long connectTcpServer (String address, int port) {
return connectTcpServer(null, 0, address, port);
}
public long connectTcpServer (String bindAddr, int bindPort, String address, int port) {
long b = createBinding();
try {
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
if (bindAddr != null)
sc.socket().bind(new InetSocketAddress (bindAddr, bindPort));
EventableSocketChannel ec = new EventableSocketChannel (sc, b, mySelector);
if (sc.connect (new InetSocketAddress (address, port))) {
// Connection returned immediately. Can happen with localhost connections.
// WARNING, this code is untested due to lack of available test conditions.
// Ought to be be able to come here from a localhost connection, but that
// doesn't happen on Linux. (Maybe on FreeBSD?)
// The reason for not handling this until we can test it is that we
// really need to return from this function WITHOUT triggering any EM events.
// That's because until the user code has seen the signature we generated here,
// it won't be able to properly dispatch them. The C++ EM deals with this
// by setting pending mode as a flag in ALL eventable descriptors and making
// the descriptor select for writable. Then, it can send UNBOUND and
// CONNECTION_COMPLETED on the next pass through the loop, because writable will
// fire.
throw new RuntimeException ("immediate-connect unimplemented");
}
else {
ec.setConnectPending();
Connections.put (b, ec);
NewConnections.add (b);
}
} catch (IOException e) {
// Can theoretically come here if a connect failure can be determined immediately.
// I don't know how to make that happen for testing purposes.
throw new RuntimeException ("immediate-connect unimplemented: " + e.toString());
}
return b;
}
public void closeConnection (long sig, boolean afterWriting) {
EventableChannel ec = Connections.get(sig);
if (ec != null)
if (ec.scheduleClose (afterWriting))
UnboundConnections.add (sig);
}
long createBinding() {
return ++BindingIndex;
}
public void signalLoopbreak() {
loopBreaker.set(true);
if (mySelector != null)
mySelector.wakeup();
}
public void startTls (long sig) throws NoSuchAlgorithmException, KeyManagementException {
Connections.get(sig).startTls();
}
public void setTimerQuantum (int mills) {
if (mills < 5 || mills > 2500)
throw new RuntimeException ("attempt to set invalid timer-quantum value: "+mills);
timerQuantum = mills;
}
public Object[] getPeerName (long sig) {
EventableChannel channel = Connections.get(sig);
if (channel != null) {
return Connections.get(sig).getPeerName();
}
else {
ServerSocketChannel acceptor = Acceptors.get(sig);
return new Object[] { acceptor.socket().getLocalPort(),
acceptor.socket().getInetAddress().getHostAddress() };
}
}
public Object[] getSockName (long sig) {
EventableChannel channel = Connections.get(sig);
if (channel != null) {
return Connections.get(sig).getSockName();
}
else {
ServerSocketChannel acceptor = Acceptors.get(sig);
return new Object[] { acceptor.socket().getLocalPort(),
acceptor.socket().getInetAddress().getHostAddress() };
}
}
public long attachChannel (SocketChannel sc, boolean watch_mode) {
long b = createBinding();
EventableSocketChannel ec = new EventableSocketChannel (sc, b, mySelector);
ec.setAttached();
if (watch_mode)
ec.setWatchOnly();
Connections.put (b, ec);
NewConnections.add (b);
return b;
}
public SocketChannel detachChannel (long sig) {
EventableSocketChannel ec = (EventableSocketChannel) Connections.get (sig);
if (ec != null) {
UnboundConnections.add (sig);
return ec.getChannel();
} else {
return null;
}
}
public void setNotifyReadable (long sig, boolean mode) {
((EventableSocketChannel) Connections.get(sig)).setNotifyReadable(mode);
}
public void setNotifyWritable (long sig, boolean mode) {
((EventableSocketChannel) Connections.get(sig)).setNotifyWritable(mode);
}
public boolean isNotifyReadable (long sig) {
return Connections.get(sig).isNotifyReadable();
}
public boolean isNotifyWritable (long sig) {
return Connections.get(sig).isNotifyWritable();
}
public boolean pauseConnection (long sig) {
return ((EventableSocketChannel) Connections.get(sig)).pause();
}
public boolean resumeConnection (long sig) {
return ((EventableSocketChannel) Connections.get(sig)).resume();
}
public boolean isConnectionPaused (long sig) {
return ((EventableSocketChannel) Connections.get(sig)).isPaused();
}
public long getOutboundDataSize (long sig) {
return Connections.get(sig).getOutboundDataSize();
}
public int getConnectionCount() {
return Connections.size() + Acceptors.size();
}
}
@@ -0,0 +1,40 @@
/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage:: http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/
package com.rubyeventmachine;
/**
* @author francis
*
*/
public class EmReactorException extends Exception {
static final long serialVersionUID = 0;
public EmReactorException (String msg) {
super (msg);
}
}
@@ -0,0 +1,72 @@
/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage:: http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/
package com.rubyeventmachine;
import java.nio.ByteBuffer;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
public interface EventableChannel {
public void scheduleOutboundData (ByteBuffer bb);
public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort);
public boolean scheduleClose (boolean afterWriting);
public void startTls();
public long getBinding();
public void readInboundData (ByteBuffer dst) throws IOException;
public void register() throws ClosedChannelException;
/**
* This is called by the reactor after it finishes running.
* The idea is to free network resources.
*/
public void close();
public boolean writeOutboundData() throws IOException;
public long getOutboundDataSize();
public void setCommInactivityTimeout (long seconds);
public Object[] getPeerName();
public Object[] getSockName();
public boolean isWatchOnly();
public boolean isNotifyReadable();
public boolean isNotifyWritable();
}
@@ -0,0 +1,201 @@
/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage:: http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/
package com.rubyeventmachine;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.DatagramChannel;
import java.util.LinkedList;
import java.io.*;
import java.net.*;
public class EventableDatagramChannel implements EventableChannel {
class Packet {
public ByteBuffer bb;
public SocketAddress recipient;
public Packet (ByteBuffer _bb, SocketAddress _recipient) {
bb = _bb;
recipient = _recipient;
}
}
DatagramChannel channel;
long binding;
Selector selector;
boolean bCloseScheduled;
LinkedList<Packet> outboundQ;
long outboundS;
SocketAddress returnAddress;
public EventableDatagramChannel (DatagramChannel dc, long _binding, Selector sel) throws ClosedChannelException {
channel = dc;
binding = _binding;
selector = sel;
bCloseScheduled = false;
outboundQ = new LinkedList<Packet>();
outboundS = 0;
dc.register(selector, SelectionKey.OP_READ, this);
}
public void scheduleOutboundData (ByteBuffer bb) {
try {
if ((!bCloseScheduled) && (bb.remaining() > 0)) {
outboundQ.addLast(new Packet(bb, returnAddress));
outboundS += bb.remaining();
channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
}
} catch (ClosedChannelException e) {
throw new RuntimeException ("no outbound data");
}
}
public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) {
try {
if ((!bCloseScheduled) && (bb.remaining() > 0)) {
outboundQ.addLast(new Packet (bb, new InetSocketAddress (recipAddress, recipPort)));
outboundS += bb.remaining();
channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
}
} catch (ClosedChannelException e) {
throw new RuntimeException ("no outbound data");
}
}
public boolean scheduleClose (boolean afterWriting) {
System.out.println ("NOT SCHEDULING CLOSE ON DATAGRAM");
return false;
}
public void startTls() {
throw new RuntimeException ("TLS is unimplemented on this Channel");
}
public long getBinding() {
return binding;
}
public void register() throws ClosedChannelException {
// TODO
}
/**
* Terminate with extreme prejudice. Don't assume there will be another pass through
* the reactor core.
*/
public void close() {
try {
channel.close();
} catch (IOException e) {
}
}
public void readInboundData (ByteBuffer dst) {
returnAddress = null;
try {
// If there is no datagram available (we're nonblocking after all),
// then channel.receive returns null.
returnAddress = channel.receive(dst);
} catch (IOException e) {
// probably a no-op. The caller will see the empty (or even partial) buffer
// and presumably do the right thing.
}
}
public boolean writeOutboundData() {
while (!outboundQ.isEmpty()) {
Packet p = outboundQ.getFirst();
int written = 0;
try {
// With a datagram socket, it's ok to send an empty buffer.
written = channel.send(p.bb, p.recipient);
outboundS -= written;
}
catch (IOException e) {
return false;
}
/* Did we consume the whole outbound buffer? If yes, pop it off and
* keep looping. If no, the outbound network buffers are full, so break
* out of here. There's a flaw that affects outbound buffers that are intentionally
* empty. We can tell whether they got sent or not. So we assume they were.
* TODO: As implemented, this ALWAYS discards packets if they were at least
* partially written. This matches the behavior of the C++ EM. My judgment
* is that this is less surprising than fragmenting the data and sending multiple
* packets would be. I could be wrong, so this is subject to change.
*/
if ((written > 0) || (p.bb.remaining() == 0))
outboundQ.removeFirst();
else
break;
}
if (outboundQ.isEmpty()) {
try {
channel.register(selector, SelectionKey.OP_READ, this);
} catch (ClosedChannelException e) {}
}
// ALWAYS drain the outbound queue before triggering a connection close.
// If anyone wants to close immediately, they're responsible for clearing
// the outbound queue.
return (bCloseScheduled && outboundQ.isEmpty()) ? false : true;
}
public void setCommInactivityTimeout (long seconds) {
// TODO
System.out.println ("DATAGRAM: SET COMM INACTIVITY UNIMPLEMENTED " + seconds);
}
public Object[] getPeerName () {
if (returnAddress != null) {
InetSocketAddress inetAddr = (InetSocketAddress) returnAddress;
return new Object[]{ inetAddr.getPort(), inetAddr.getHostName() };
} else {
return null;
}
}
public Object[] getSockName () {
DatagramSocket socket = channel.socket();
return new Object[]{ socket.getLocalPort(),
socket.getLocalAddress().getHostAddress() };
}
public boolean isWatchOnly() { return false; }
public boolean isNotifyReadable() { return false; }
public boolean isNotifyWritable() { return false; }
public long getOutboundDataSize() { return outboundS; }
}
@@ -0,0 +1,415 @@
/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage:: http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/
/**
*
*/
package com.rubyeventmachine;
/**
* @author francis
*
*/
import java.nio.channels.*;
import java.nio.*;
import java.util.*;
import java.io.*;
import java.net.Socket;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;
import java.lang.reflect.Field;
import java.security.*;
public class EventableSocketChannel implements EventableChannel {
Selector selector;
SelectionKey channelKey;
SocketChannel channel;
long binding;
LinkedList<ByteBuffer> outboundQ;
long outboundS;
boolean bCloseScheduled;
boolean bConnectPending;
boolean bWatchOnly;
boolean bAttached;
boolean bNotifyReadable;
boolean bNotifyWritable;
boolean bPaused;
SSLEngine sslEngine;
SSLContext sslContext;
public EventableSocketChannel (SocketChannel sc, long _binding, Selector sel) {
channel = sc;
binding = _binding;
selector = sel;
bCloseScheduled = false;
bConnectPending = false;
bWatchOnly = false;
bAttached = false;
bNotifyReadable = false;
bNotifyWritable = false;
outboundQ = new LinkedList<ByteBuffer>();
outboundS = 0;
}
public long getBinding() {
return binding;
}
public SocketChannel getChannel() {
return channel;
}
public void register() throws ClosedChannelException {
if (channelKey == null) {
int events = currentEvents();
channelKey = channel.register(selector, events, this);
}
}
/**
* Terminate with extreme prejudice. Don't assume there will be another pass through
* the reactor core.
*/
public void close() {
if (channelKey != null) {
channelKey.cancel();
channelKey = null;
}
if (bAttached) {
// attached channels are copies, so reset the file descriptor to prevent java from close()ing it
Field f;
FileDescriptor fd;
try {
/* do _NOT_ clobber fdVal here, it will break epoll/kqueue on jdk6!
* channelKey.cancel() above does not occur until the next call to select
* and if fdVal is gone, we will continue to get events for this fd.
*
* instead, remove fdVal in cleanup(), which is processed via DetachedConnections,
* after UnboundConnections but before NewConnections.
*/
f = channel.getClass().getDeclaredField("fd");
f.setAccessible(true);
fd = (FileDescriptor) f.get(channel);
f = fd.getClass().getDeclaredField("fd");
f.setAccessible(true);
f.set(fd, -1);
} catch (java.lang.NoSuchFieldException e) {
e.printStackTrace();
} catch (java.lang.IllegalAccessException e) {
e.printStackTrace();
}
return;
}
try {
channel.close();
} catch (IOException e) {
}
}
public void cleanup() {
if (bAttached) {
Field f;
try {
f = channel.getClass().getDeclaredField("fdVal");
f.setAccessible(true);
f.set(channel, -1);
} catch (java.lang.NoSuchFieldException e) {
e.printStackTrace();
} catch (java.lang.IllegalAccessException e) {
e.printStackTrace();
}
}
channel = null;
}
public void scheduleOutboundData (ByteBuffer bb) {
if (!bCloseScheduled && bb.remaining() > 0) {
if (sslEngine != null) {
try {
ByteBuffer b = ByteBuffer.allocate(32*1024); // TODO, preallocate this buffer.
sslEngine.wrap(bb, b);
b.flip();
outboundQ.addLast(b);
outboundS += b.remaining();
} catch (SSLException e) {
throw new RuntimeException ("ssl error");
}
}
else {
outboundQ.addLast(bb);
outboundS += bb.remaining();
}
updateEvents();
}
}
public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) {
throw new RuntimeException ("datagram sends not supported on this channel");
}
/**
* Called by the reactor when we have selected readable.
*/
public void readInboundData (ByteBuffer bb) throws IOException {
if (channel.read(bb) == -1)
throw new IOException ("eof");
}
public long getOutboundDataSize() { return outboundS; }
/**
* Called by the reactor when we have selected writable.
* Return false to indicate an error that should cause the connection to close.
* TODO, VERY IMPORTANT: we're here because we selected writable, but it's always
* possible to become unwritable between the poll and when we get here. The way
* this code is written, we're depending on a nonblocking write NOT TO CONSUME
* the whole outbound buffer in this case, rather than firing an exception.
* We should somehow verify that this is indeed Java's defined behavior.
* @return
*/
public boolean writeOutboundData() throws IOException {
ByteBuffer[] bufs = new ByteBuffer[64];
int i;
long written, toWrite;
while (!outboundQ.isEmpty()) {
i = 0;
toWrite = 0;
written = 0;
while (i < 64 && !outboundQ.isEmpty()) {
bufs[i] = outboundQ.removeFirst();
toWrite += bufs[i].remaining();
i++;
}
if (toWrite > 0)
written = channel.write(bufs, 0, i);
outboundS -= written;
// Did we consume the whole outbound buffer? If yes,
// pop it off and keep looping. If no, the outbound network
// buffers are full, so break out of here.
if (written < toWrite) {
while (i > 0 && bufs[i-1].remaining() > 0) {
outboundQ.addFirst(bufs[i-1]);
i--;
}
break;
}
}
if (outboundQ.isEmpty() && !bCloseScheduled) {
updateEvents();
}
// ALWAYS drain the outbound queue before triggering a connection close.
// If anyone wants to close immediately, they're responsible for clearing
// the outbound queue.
return (bCloseScheduled && outboundQ.isEmpty()) ? false : true;
}
public void setConnectPending() {
bConnectPending = true;
updateEvents();
}
/**
* Called by the reactor when we have selected connectable.
* Return false to indicate an error that should cause the connection to close.
*/
public boolean finishConnecting() throws IOException {
channel.finishConnect();
bConnectPending = false;
updateEvents();
return true;
}
public boolean scheduleClose (boolean afterWriting) {
// TODO: What the hell happens here if bConnectPending is set?
if (!afterWriting) {
outboundQ.clear();
outboundS = 0;
}
if (outboundQ.isEmpty())
return true;
else {
updateEvents();
bCloseScheduled = true;
return false;
}
}
public void startTls() {
if (sslEngine == null) {
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null); // TODO, fill in the parameters.
sslEngine = sslContext.createSSLEngine(); // TODO, should use the parameterized version, to get Kerb stuff and session re-use.
sslEngine.setUseClientMode(false);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this.
} catch (KeyManagementException e) {
throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this.
}
}
System.out.println ("Starting TLS");
}
public ByteBuffer dispatchInboundData (ByteBuffer bb) throws SSLException {
if (sslEngine != null) {
if (true) throw new RuntimeException ("TLS currently unimplemented");
System.setProperty("javax.net.debug", "all");
ByteBuffer w = ByteBuffer.allocate(32*1024); // TODO, WRONG, preallocate this buffer.
SSLEngineResult res = sslEngine.unwrap(bb, w);
if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
Runnable r;
while ((r = sslEngine.getDelegatedTask()) != null) {
r.run();
}
}
System.out.println (bb);
w.flip();
return w;
}
else
return bb;
}
public void setCommInactivityTimeout (long seconds) {
// TODO
System.out.println ("SOCKET: SET COMM INACTIVITY UNIMPLEMENTED " + seconds);
}
public Object[] getPeerName () {
Socket sock = channel.socket();
return new Object[]{ sock.getPort(), sock.getInetAddress().getHostAddress() };
}
public Object[] getSockName () {
Socket sock = channel.socket();
return new Object[]{ sock.getLocalPort(),
sock.getLocalAddress().getHostAddress() };
}
public void setWatchOnly() {
bWatchOnly = true;
updateEvents();
}
public boolean isWatchOnly() { return bWatchOnly; }
public void setAttached() {
bAttached = true;
}
public boolean isAttached() { return bAttached; }
public void setNotifyReadable (boolean mode) {
bNotifyReadable = mode;
updateEvents();
}
public boolean isNotifyReadable() { return bNotifyReadable; }
public void setNotifyWritable (boolean mode) {
bNotifyWritable = mode;
updateEvents();
}
public boolean isNotifyWritable() { return bNotifyWritable; }
public boolean pause() {
if (bWatchOnly) {
throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead");
}
boolean old = bPaused;
bPaused = true;
updateEvents();
return !old;
}
public boolean resume() {
if (bWatchOnly) {
throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead");
}
boolean old = bPaused;
bPaused = false;
updateEvents();
return old;
}
public boolean isPaused() {
return bPaused;
}
private void updateEvents() {
if (channelKey == null)
return;
int events = currentEvents();
if (channelKey.interestOps() != events) {
channelKey.interestOps(events);
}
}
private int currentEvents() {
int events = 0;
if (bWatchOnly)
{
if (bNotifyReadable)
events |= SelectionKey.OP_READ;
if (bNotifyWritable)
events |= SelectionKey.OP_WRITE;
}
else if (!bPaused)
{
if (bConnectPending)
events |= SelectionKey.OP_CONNECT;
else {
events |= SelectionKey.OP_READ;
if (!outboundQ.isEmpty())
events |= SelectionKey.OP_WRITE;
}
}
return events;
}
}
@@ -0,0 +1,59 @@
# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
# by default. It allows input to be spoon-fed from some outside source which
# receives arbitrary length datagrams which may-or-may-not contain the token
# by which entities are delimited. In this respect it's ideally paired with
# something like EventMachine (http://rubyeventmachine.com/).
class BufferedTokenizer
# New BufferedTokenizers will operate on lines delimited by a delimiter,
# which is by default the global input delimiter $/ ("\n").
#
# The input buffer is stored as an array. This is by far the most efficient
# approach given language constraints (in C a linked list would be a more
# appropriate data structure). Segments of input data are stored in a list
# which is only joined when a token is reached, substantially reducing the
# number of objects required for the operation.
def initialize(delimiter = $/)
@delimiter = delimiter
@input = []
@tail = ''
@trim = @delimiter.length - 1
end
# Extract takes an arbitrary string of input data and returns an array of
# tokenized entities, provided there were any available to extract. This
# makes for easy processing of datagrams using a pattern like:
#
# tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
#
# Using -1 makes split to return "" if the token is at the end of
# the string, meaning the last element is the start of the next chunk.
def extract(data)
if @trim > 0
tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short
data = tail_end + data if tail_end
end
@input << @tail
entities = data.split(@delimiter, -1)
@tail = entities.shift
unless entities.empty?
@input << @tail
entities.unshift @input.join
@input.clear
@tail = entities.pop
end
entities
end
# Flush the contents of the input buffer, i.e. return the input buffer even though
# a token has not yet been encountered
def flush
@input << @tail
buffer = @input.join
@input.clear
@tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7
buffer
end
end
@@ -0,0 +1,58 @@
module EventMachine
# Utility method for coercing arguments to an object that responds to :call.
# Accepts an object and a method name to send to, or a block, or an object
# that responds to :call.
#
# @example EventMachine.Callback used with a block. Returns that block.
#
# cb = EventMachine.Callback do |msg|
# puts(msg)
# end
# # returned object is a callable
# cb.call('hello world')
#
#
# @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call
#
# cb = EventMachine.Callback(Object, :puts)
# # returned object is a callable that delegates to Kernel#puts (in this case Object.puts)
# cb.call('hello world')
#
#
# @example EventMachine.Callback used with an object that responds to #call. Returns the argument.
#
# cb = EventMachine.Callback(proc{ |msg| puts(msg) })
# # returned object is a callable
# cb.call('hello world')
#
#
# @overload Callback(object, method)
# Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method
# @param [Object] Object to invoke method on
# @param [Symbol] Method name
# @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments
#
# @overload Callback(object)
# Returns callable object as is, without any coercion
# @param [<#call>] An object that responds to #call
# @return [<#call>] Its argument
#
# @overload Callback(&block)
# Returns block passed to it without any coercion
# @return [<#call>] Block passed to this method
#
# @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given
#
# @return [<#call>]
def self.Callback(object = nil, method = nil, &blk)
if object && method
lambda { |*args| object.__send__ method, *args }
else
if object.respond_to? :call
object
else
blk || raise(ArgumentError)
end # if
end # if
end # self.Callback
end # EventMachine
@@ -0,0 +1,69 @@
module EventMachine
# Provides a simple thread-safe way to transfer data between (typically) long running
# tasks in {EventMachine.defer} and event loop thread.
#
# @example
#
# channel = EventMachine::Channel.new
# sid = channel.subscribe { |msg| p [:got, msg] }
#
# channel.push('hello world')
# channel.unsubscribe(sid)
#
#
class Channel
def initialize
@subs = {}
@uid = 0
end
# Return the number of current subscribers.
def num_subscribers
return @subs.size
end
# Takes any arguments suitable for EM::Callback() and returns a subscriber
# id for use when unsubscribing.
#
# @return [Integer] Subscribe identifier
# @see #unsubscribe
def subscribe(*a, &b)
name = gen_id
EM.schedule { @subs[name] = EM::Callback(*a, &b) }
name
end
# Removes subscriber from the list.
#
# @param [Integer] Subscriber identifier
# @see #subscribe
def unsubscribe(name)
EM.schedule { @subs.delete name }
end
# Add items to the channel, which are pushed out to all subscribers.
def push(*items)
items = items.dup
EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } }
end
alias << push
# Fetches one message from the channel.
def pop(*a, &b)
EM.schedule {
name = subscribe do |*args|
unsubscribe(name)
EM::Callback(*a, &b).call(*args)
end
}
end
private
# @private
def gen_id
@uid += 1
end
end
end
@@ -0,0 +1,304 @@
# = EM::Completion
#
# A completion is a callback container for various states of completion. In
# its most basic form it has a start state and a finish state.
#
# This implementation includes some hold-back from the EM::Deferrable
# interface in order to be compatible - but it has a much cleaner
# implementation.
#
# In general it is preferred that this implementation be used as a state
# callback container than EM::DefaultDeferrable or other classes including
# EM::Deferrable. This is because it is generally more sane to keep this level
# of state in a dedicated state-back container. This generally leads to more
# malleable interfaces and software designs, as well as eradicating nasty bugs
# that result from abstraction leakage.
#
# == Basic Usage
#
# As already mentioned, the basic usage of a Completion is simply for its two
# final states, :succeeded and :failed.
#
# An asynchronous operation will complete at some future point in time, and
# users often want to react to this event. API authors will want to expose
# some common interface to react to these events.
#
# In the following example, the user wants to know when a short lived
# connection has completed its exchange with the remote server. The simple
# protocol just waits for an ack to its message.
#
# class Protocol < EM::Connection
# include EM::P::LineText2
#
# def initialize(message, completion)
# @message, @completion = message, completion
# @completion.completion { close_connection }
# @completion.timeout(1, :timeout)
# end
#
# def post_init
# send_data(@message)
# end
#
# def receive_line(line)
# case line
# when /ACK/i
# @completion.succeed line
# when /ERR/i
# @completion.fail :error, line
# else
# @completion.fail :unknown, line
# end
# end
#
# def unbind
# @completion.fail :disconnected unless @completion.completed?
# end
# end
#
# class API
# attr_reader :host, :port
#
# def initialize(host = 'example.org', port = 8000)
# @host, @port = host, port
# end
#
# def request(message)
# completion = EM::Deferrable::Completion.new
# EM.connect(host, port, Protocol, message, completion)
# completion
# end
# end
#
# api = API.new
# completion = api.request('stuff')
# completion.callback do |line|
# puts "API responded with: #{line}"
# end
# completion.errback do |type, line|
# case type
# when :error
# puts "API error: #{line}"
# when :unknown
# puts "API returned unknown response: #{line}"
# when :disconnected
# puts "API server disconnected prematurely"
# when :timeout
# puts "API server did not respond in a timely fashion"
# end
# end
#
# == Advanced Usage
#
# This completion implementation also supports more state callbacks and
# arbitrary states (unlike the original Deferrable API). This allows for basic
# stateful process encapsulation. One might use this to setup state callbacks
# for various states in an exchange like in the basic usage example, except
# where the applicaiton could be made to react to "connected" and
# "disconnected" states additionally.
#
# class Protocol < EM::Connection
# def initialize(completion)
# @response = []
# @completion = completion
# @completion.stateback(:disconnected) do
# @completion.succeed @response.join
# end
# end
#
# def connection_completed
# @host, @port = Socket.unpack_sockaddr_in get_peername
# @completion.change_state(:connected, @host, @port)
# send_data("GET http://example.org/ HTTP/1.0\r\n\r\n")
# end
#
# def receive_data(data)
# @response << data
# end
#
# def unbind
# @completion.change_state(:disconnected, @host, @port)
# end
# end
#
# completion = EM::Deferrable::Completion.new
# completion.stateback(:connected) do |host, port|
# puts "Connected to #{host}:#{port}"
# end
# completion.stateback(:disconnected) do |host, port|
# puts "Disconnected from #{host}:#{port}"
# end
# completion.callback do |response|
# puts response
# end
#
# EM.connect('example.org', 80, Protocol, completion)
#
# == Timeout
#
# The Completion also has a timeout. The timeout is global and is not aware of
# states apart from completion states. The timeout is only engaged if #timeout
# is called, and it will call fail if it is reached.
#
# == Completion states
#
# By default there are two completion states, :succeeded and :failed. These
# states can be modified by subclassing and overrding the #completion_states
# method. Completion states are special, in that callbacks for all completion
# states are explcitly cleared when a completion state is entered. This
# prevents errors that could arise from accidental unterminated timeouts, and
# other such user errors.
#
# == Other notes
#
# Several APIs have been carried over from EM::Deferrable for compatibility
# reasons during a transitionary period. Specifically cancel_errback and
# cancel_callback are implemented, but their usage is to be strongly
# discouraged. Due to the already complex nature of reaction systems, dynamic
# callback deletion only makes the problem much worse. It is always better to
# add correct conditionals to the callback code, or use more states, than to
# address such implementaiton issues with conditional callbacks.
module EventMachine
class Completion
# This is totally not used (re-implemented), it's here in case people check
# for kind_of?
include EventMachine::Deferrable
attr_reader :state, :value
def initialize
@state = :unknown
@callbacks = Hash.new { |h,k| h[k] = [] }
@value = []
@timeout_timer = nil
end
# Enter the :succeeded state, setting the result value if given.
def succeed(*args)
change_state(:succeeded, *args)
end
# The old EM method:
alias set_deferred_success succeed
# Enter the :failed state, setting the result value if given.
def fail(*args)
change_state(:failed, *args)
end
# The old EM method:
alias set_deferred_failure fail
# Statebacks are called when you enter (or are in) the named state.
def stateback(state, *a, &b)
# The following is quite unfortunate special casing for :completed
# statebacks, but it's a necessary evil for latent completion
# definitions.
if :completed == state || !completed? || @state == state
@callbacks[state] << EM::Callback(*a, &b)
end
execute_callbacks
self
end
# Callbacks are called when you enter (or are in) a :succeeded state.
def callback(*a, &b)
stateback(:succeeded, *a, &b)
end
# Errbacks are called when you enter (or are in) a :failed state.
def errback(*a, &b)
stateback(:failed, *a, &b)
end
# Completions are called when you enter (or are in) either a :failed or a
# :succeeded state. They are stored as a special (reserved) state called
# :completed.
def completion(*a, &b)
stateback(:completed, *a, &b)
end
# Enter a new state, setting the result value if given. If the state is one
# of :succeeded or :failed, then :completed callbacks will also be called.
def change_state(state, *args)
@value = args
@state = state
EM.schedule { execute_callbacks }
end
# The old EM method:
alias set_deferred_status change_state
# Indicates that we've reached some kind of completion state, by default
# this is :succeeded or :failed. Due to these semantics, the :completed
# state is reserved for internal use.
def completed?
completion_states.any? { |s| state == s }
end
# Completion states simply returns a list of completion states, by default
# this is :succeeded and :failed.
def completion_states
[:succeeded, :failed]
end
# Schedule a time which if passes before we enter a completion state, this
# deferrable will be failed with the given arguments.
def timeout(time, *args)
cancel_timeout
@timeout_timer = EM::Timer.new(time) do
fail(*args) unless completed?
end
end
# Disable the timeout
def cancel_timeout
if @timeout_timer
@timeout_timer.cancel
@timeout_timer = nil
end
end
# Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT
# recommended, this is an anti-pattern.
def cancel_errback(*a, &b)
@callbacks[:failed].delete(EM::Callback(*a, &b))
end
# Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT
# recommended, this is an anti-pattern.
def cancel_callback(*a, &b)
@callbacks[:succeeded].delete(EM::Callback(*a, &b))
end
private
# Execute all callbacks for the current state. If in a completed state, then
# call any statebacks associated with the completed state.
def execute_callbacks
execute_state_callbacks(state)
if completed?
execute_state_callbacks(:completed)
clear_dead_callbacks
cancel_timeout
end
end
# Iterate all callbacks for a given state, and remove then call them.
def execute_state_callbacks(state)
while callback = @callbacks[state].shift
callback.call(*value)
end
end
# If we enter a completion state, clear other completion states after all
# callback chains are completed. This means that operation specific
# callbacks can't be dual-called, which is most common user error.
def clear_dead_callbacks
completion_states.each do |state|
@callbacks[state].clear
end
end
end
end
@@ -0,0 +1,770 @@
module EventMachine
class FileNotFoundException < Exception
end
# EventMachine::Connection is a class that is instantiated
# by EventMachine's processing loop whenever a new connection
# is created. (New connections can be either initiated locally
# to a remote server or accepted locally from a remote client.)
# When a Connection object is instantiated, it <i>mixes in</i>
# the functionality contained in the user-defined module
# specified in calls to {EventMachine.connect} or {EventMachine.start_server}.
# User-defined handler modules may redefine any or all of the standard
# methods defined here, as well as add arbitrary additional code
# that will also be mixed in.
#
# EventMachine manages one object inherited from EventMachine::Connection
# (and containing the mixed-in user code) for every network connection
# that is active at any given time.
# The event loop will automatically call methods on EventMachine::Connection
# objects whenever specific events occur on the corresponding connections,
# as described below.
#
# This class is never instantiated by user code, and does not publish an
# initialize method. The instance methods of EventMachine::Connection
# which may be called by the event loop are:
#
# * {#post_init}
# * {#connection_completed}
# * {#receive_data}
# * {#unbind}
# * {#ssl_verify_peer} (if TLS is used)
# * {#ssl_handshake_completed}
#
# All of the other instance methods defined here are called only by user code.
#
# @see file:docs/GettingStarted.md EventMachine tutorial
class Connection
# @private
attr_accessor :signature
# @private
alias original_method method
# Override .new so subclasses don't have to call super and can ignore
# connection-specific arguments
#
# @private
def self.new(sig, *args)
allocate.instance_eval do
# Store signature
@signature = sig
# associate_callback_target sig
# Call a superclass's #initialize if it has one
initialize(*args)
# post initialize callback
post_init
self
end
end
# Stubbed initialize so legacy superclasses can safely call super
#
# @private
def initialize(*args)
end
# Called by the event loop immediately after the network connection has been established,
# and before resumption of the network loop.
# This method is generally not called by user code, but is called automatically
# by the event loop. The base-class implementation is a no-op.
# This is a very good place to initialize instance variables that will
# be used throughout the lifetime of the network connection.
#
# @see #connection_completed
# @see #unbind
# @see #send_data
# @see #receive_data
def post_init
end
# Called by the event loop whenever data has been received by the network connection.
# It is never called by user code. {#receive_data} is called with a single parameter, a String containing
# the network protocol data, which may of course be binary. You will
# generally redefine this method to perform your own processing of the incoming data.
#
# Here's a key point which is essential to understanding the event-driven
# programming model: <i>EventMachine knows absolutely nothing about the protocol
# which your code implements.</i> You must not make any assumptions about
# the size of the incoming data packets, or about their alignment on any
# particular intra-message or PDU boundaries (such as line breaks).
# receive_data can and will send you arbitrary chunks of data, with the
# only guarantee being that the data is presented to your code in the order
# it was collected from the network. Don't even assume that the chunks of
# data will correspond to network packets, as EventMachine can and will coalesce
# several incoming packets into one, to improve performance. The implication for your
# code is that you generally will need to implement some kind of a state machine
# in your redefined implementation of receive_data. For a better understanding
# of this, read through the examples of specific protocol handlers in EventMachine::Protocols
#
# The base-class implementation (which will be invoked only if you didn't override it in your protocol handler)
# simply prints incoming data packet size to stdout.
#
# @param [String] data Opaque incoming data.
# @note Depending on the protocol, buffer sizes and OS networking stack configuration, incoming data may or may not be "a complete message".
# It is up to this handler to detect content boundaries to determine whether all the content (for example, full HTTP request)
# has been received and can be processed.
#
# @see #post_init
# @see #connection_completed
# @see #unbind
# @see #send_data
# @see file:docs/GettingStarted.md EventMachine tutorial
def receive_data data
puts "............>>>#{data.length}"
end
# Called by EventMachine when the SSL/TLS handshake has
# been completed, as a result of calling #start_tls to initiate SSL/TLS on the connection.
#
# This callback exists because {#post_init} and {#connection_completed} are **not** reliable
# for indicating when an SSL/TLS connection is ready to have its certificate queried for.
#
# @see #get_peer_cert
def ssl_handshake_completed
end
# Called by EventMachine when :verify_peer => true has been passed to {#start_tls}.
# It will be called with each certificate in the certificate chain provided by the remote peer.
#
# The cert will be passed as a String in PEM format, the same as in {#get_peer_cert}. It is up to user defined
# code to perform a check on the certificates. The return value from this callback is used to accept or deny the peer.
# A return value that is not nil or false triggers acceptance. If the peer is not accepted, the connection
# will be subsequently closed.
#
# @example This server always accepts all peers
#
# module AcceptServer
# def post_init
# start_tls(:verify_peer => true)
# end
#
# def ssl_verify_peer(cert)
# true
# end
#
# def ssl_handshake_completed
# $server_handshake_completed = true
# end
# end
#
#
# @example This server never accepts any peers
#
# module DenyServer
# def post_init
# start_tls(:verify_peer => true)
# end
#
# def ssl_verify_peer(cert)
# # Do not accept the peer. This should now cause the connection to shut down
# # without the SSL handshake being completed.
# false
# end
#
# def ssl_handshake_completed
# $server_handshake_completed = true
# end
# end
#
# @see #start_tls
def ssl_verify_peer(cert)
end
# called by the framework whenever a connection (either a server or client connection) is closed.
# The close can occur because your code intentionally closes it (using {#close_connection} and {#close_connection_after_writing}),
# because the remote peer closed the connection, or because of a network error.
# You may not assume that the network connection is still open and able to send or
# receive data when the callback to unbind is made. This is intended only to give
# you a chance to clean up associations your code may have made to the connection
# object while it was open.
#
# If you want to detect which peer has closed the connection, you can override {#close_connection} in your protocol handler
# and set an @ivar.
#
# @example Overriding Connection#close_connection to distinguish connections closed on our side
#
# class MyProtocolHandler < EventMachine::Connection
#
# # ...
#
# def close_connection(*args)
# @intentionally_closed_connection = true
# super(*args)
# end
#
# def unbind
# if @intentionally_closed_connection
# # ...
# end
# end
#
# # ...
#
# end
#
# @see #post_init
# @see #connection_completed
# @see file:docs/GettingStarted.md EventMachine tutorial
def unbind
end
# Called by the reactor after attempting to relay incoming data to a descriptor (set as a proxy target descriptor with
# {EventMachine.enable_proxy}) that has already been closed.
#
# @see EventMachine.enable_proxy
def proxy_target_unbound
end
# called when the reactor finished proxying all
# of the requested bytes.
def proxy_completed
end
# EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up
# a low-level proxy relay for all data inbound for this connection, to the connection given
# as the argument. This is essentially just a helper method for enable_proxy.
#
# @see EventMachine.enable_proxy
def proxy_incoming_to(conn,bufsize=0)
EventMachine::enable_proxy(self, conn, bufsize)
end
# A helper method for {EventMachine.disable_proxy}
def stop_proxying
EventMachine::disable_proxy(self)
end
# The number of bytes proxied to another connection. Reset to zero when
# EventMachine::Connection#proxy_incoming_to is called, and incremented whenever data is proxied.
def get_proxied_bytes
EventMachine::get_proxied_bytes(@signature)
end
# EventMachine::Connection#close_connection is called only by user code, and never
# by the event loop. You may call this method against a connection object in any
# callback handler, whether or not the callback was made against the connection
# you want to close. close_connection <i>schedules</i> the connection to be closed
# at the next available opportunity within the event loop. You may not assume that
# the connection is closed when close_connection returns. In particular, the framework
# will callback the unbind method for the particular connection at a point shortly
# after you call close_connection. You may assume that the unbind callback will
# take place sometime after your call to close_connection completes. In other words,
# the unbind callback will not re-enter your code "inside" of your call to close_connection.
# However, it's not guaranteed that a future version of EventMachine will not change
# this behavior.
#
# {#close_connection} will *silently discard* any outbound data which you have
# sent to the connection using {EventMachine::Connection#send_data} but which has not
# yet been sent across the network. If you want to avoid this behavior, use
# {EventMachine::Connection#close_connection_after_writing}.
#
def close_connection after_writing = false
EventMachine::close_connection @signature, after_writing
end
# Removes given connection from the event loop.
# The connection's socket remains open and its file descriptor number is returned.
def detach
EventMachine::detach_fd @signature
end
def get_sock_opt level, option
EventMachine::get_sock_opt @signature, level, option
end
def set_sock_opt level, optname, optval
EventMachine::set_sock_opt @signature, level, optname, optval
end
# A variant of {#close_connection}.
# All of the descriptive comments given for close_connection also apply to
# close_connection_after_writing, *with one exception*: if the connection has
# outbound data sent using send_dat but which has not yet been sent across the network,
# close_connection_after_writing will schedule the connection to be closed *after*
# all of the outbound data has been safely written to the remote peer.
#
# Depending on the amount of outgoing data and the speed of the network,
# considerable time may elapse between your call to close_connection_after_writing
# and the actual closing of the socket (at which time the unbind callback will be called
# by the event loop). During this time, you *may not* call send_data to transmit
# additional data (that is, the connection is closed for further writes). In very
# rare cases, you may experience a receive_data callback after your call to {#close_connection_after_writing},
# depending on whether incoming data was in the process of being received on the connection
# at the moment when you called {#close_connection_after_writing}. Your protocol handler must
# be prepared to properly deal with such data (probably by ignoring it).
#
# @see #close_connection
# @see #send_data
def close_connection_after_writing
close_connection true
end
# Call this method to send data to the remote end of the network connection. It takes a single String argument,
# which may contain binary data. Data is buffered to be sent at the end of this event loop tick (cycle).
#
# When used in a method that is event handler (for example, {#post_init} or {#connection_completed}, it will send
# data to the other end of the connection that generated the event.
# You can also call {#send_data} to write to other connections. For more information see The Chat Server Example in the
# {file:docs/GettingStarted.md EventMachine tutorial}.
#
# If you want to send some data and then immediately close the connection, make sure to use {#close_connection_after_writing}
# instead of {#close_connection}.
#
#
# @param [String] data Data to send asynchronously
#
# @see file:docs/GettingStarted.md EventMachine tutorial
# @see Connection#receive_data
# @see Connection#post_init
# @see Connection#unbind
def send_data data
data = data.to_s
size = data.bytesize if data.respond_to?(:bytesize)
size ||= data.size
EventMachine::send_data @signature, data, size
end
# Returns true if the connection is in an error state, false otherwise.
#
# In general, you can detect the occurrence of communication errors or unexpected
# disconnection by the remote peer by handing the {#unbind} method. In some cases, however,
# it's useful to check the status of the connection using {#error?} before attempting to send data.
# This function is synchronous but it will return immediately without blocking.
#
# @return [Boolean] true if the connection is in an error state, false otherwise
def error?
errno = EventMachine::report_connection_error_status(@signature)
case errno
when 0
false
when -1
true
else
EventMachine::ERRNOS[errno]
end
end
# Called by the event loop when a remote TCP connection attempt completes successfully.
# You can expect to get this notification after calls to {EventMachine.connect}. Remember that EventMachine makes remote connections
# asynchronously, just as with any other kind of network event. This method
# is intended primarily to assist with network diagnostics. For normal protocol
# handling, use #post_init to perform initial work on a new connection (such as sending initial set of data).
# {Connection#post_init} will always be called. This method will only be called in case of a successful completion.
# A connection attempt which fails will result a call to {Connection#unbind} after the failure.
#
# @see Connection#post_init
# @see Connection#unbind
# @see file:docs/GettingStarted.md EventMachine tutorial
def connection_completed
end
# Call {#start_tls} at any point to initiate TLS encryption on connected streams.
# The method is smart enough to know whether it should perform a server-side
# or a client-side handshake. An appropriate place to call {#start_tls} is in
# your redefined {#post_init} method, or in the {#connection_completed} handler for
# an outbound connection.
#
#
# @option args [String] :cert_chain_file (nil) local path of a readable file that contants a chain of X509 certificates in
# the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail),
# with the most-resolved certificate at the top of the file, successive intermediate
# certs in the middle, and the root (or CA) cert at the bottom.
#
# @option args [String] :private_key_file (nil) local path of a readable file that must contain a private key in the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail).
#
# @option args [Boolean] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code.
# If true, the {#ssl_verify_peer} callback on the {EventMachine::Connection} object is called with each certificate
# in the certificate chain provided by the peer. See documentation on {#ssl_verify_peer} for how to use this.
#
# @option args [Boolean] :fail_if_no_peer_cert (false) Used in conjunction with verify_peer. If set the SSL handshake will be terminated if the peer does not provide a certificate.
#
#
# @option args [String] :cipher_list ("ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH") indicates the available SSL cipher values. Default value is "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH". Check the format of the OpenSSL cipher string at http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT.
#
# @option args [String] :ecdh_curve (nil) The curve for ECDHE ciphers. See available ciphers with 'openssl ecparam -list_curves'
#
# @option args [String] :dhparam (nil) The local path of a file containing DH parameters for EDH ciphers in [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail) See: 'openssl dhparam'
#
# @option args [Array] :ssl_version (TLSv1 TLSv1_1 TLSv1_2) indicates the allowed SSL/TLS versions. Possible values are: {SSLv2}, {SSLv3}, {TLSv1}, {TLSv1_1}, {TLSv1_2}.
#
# @example Using TLS with EventMachine
#
# require 'rubygems'
# require 'eventmachine'
#
# module Handler
# def post_init
# start_tls(:private_key_file => '/tmp/server.key', :cert_chain_file => '/tmp/server.crt', :verify_peer => false)
# end
# end
#
# EventMachine.run do
# EventMachine.start_server("127.0.0.1", 9999, Handler)
# end
#
# @param [Hash] args
#
# @todo support passing an encryption parameter, which can be string or Proc, to get a passphrase
# for encrypted private keys.
# @todo support passing key material via raw strings or Procs that return strings instead of
# just filenames.
#
# @see #ssl_verify_peer
def start_tls args={}
priv_key = args[:private_key_file]
cert_chain = args[:cert_chain_file]
verify_peer = args[:verify_peer]
sni_hostname = args[:sni_hostname]
cipher_list = args[:cipher_list]
ssl_version = args[:ssl_version]
ecdh_curve = args[:ecdh_curve]
dhparam = args[:dhparam]
fail_if_no_peer_cert = args[:fail_if_no_peer_cert]
[priv_key, cert_chain].each do |file|
next if file.nil? or file.empty?
raise FileNotFoundException,
"Could not find #{file} for start_tls" unless File.exist? file
end
protocols_bitmask = 0
if ssl_version.nil?
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2
else
[ssl_version].flatten.each do |p|
case p.to_s.downcase
when 'sslv2'
protocols_bitmask |= EventMachine::EM_PROTO_SSLv2
when 'sslv3'
protocols_bitmask |= EventMachine::EM_PROTO_SSLv3
when 'tlsv1'
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1
when 'tlsv1_1'
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1
when 'tlsv1_2'
protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2
else
raise("Unrecognized SSL/TLS Protocol: #{p}")
end
end
end
EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer, fail_if_no_peer_cert, sni_hostname || '', cipher_list || '', ecdh_curve || '', dhparam || '', protocols_bitmask)
EventMachine::start_tls @signature
end
# If [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is active on the connection, returns the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509)
# as a string, in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail). This can then be used for arbitrary validation
# of a peer's certificate in your code.
#
# This should be called in/after the {#ssl_handshake_completed} callback, which indicates
# that SSL/TLS is active. Using this callback is important, because the certificate may not
# be available until the time it is executed. Using #post_init or #connection_completed is
# not adequate, because the SSL handshake may still be taking place.
#
# This method will return `nil` if:
#
# * EventMachine is not built with [OpenSSL](http://www.openssl.org) support
# * [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is not active on the connection
# * TLS handshake is not yet complete
# * Remote peer for any other reason has not presented a certificate
#
#
# @example Getting peer TLS certificate information in EventMachine
#
# module Handler
# def post_init
# puts "Starting TLS"
# start_tls
# end
#
# def ssl_handshake_completed
# puts get_peer_cert
# close_connection
# end
#
# def unbind
# EventMachine::stop_event_loop
# end
# end
#
# EventMachine.run do
# EventMachine.connect "mail.google.com", 443, Handler
# end
#
# # Will output:
# # -----BEGIN CERTIFICATE-----
# # MIIDIjCCAougAwIBAgIQbldpChBPqv+BdPg4iwgN8TANBgkqhkiG9w0BAQUFADBM
# # MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
# # THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wODA1MDIxNjMyNTRaFw0w
# # OTA1MDIxNjMyNTRaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
# # MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRgw
# # FgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
# # AoGBALlkxdh2QXegdElukCSOV2+8PKiONIS+8Tu9K7MQsYpqtLNC860zwOPQ2NLI
# # 3Zp4jwuXVTrtzGuiqf5Jioh35Ig3CqDXtLyZoypjZUQcq4mlLzHlhIQ4EhSjDmA7
# # Ffw9y3ckSOQgdBQWNLbquHh9AbEUjmhkrYxIqKXeCnRKhv6nAgMBAAGjgecwgeQw
# # KAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwNgYDVR0f
# # BC8wLTAroCmgJ4YlaHR0cDovL2NybC50aGF3dGUuY29tL1RoYXd0ZVNHQ0NBLmNy
# # bDByBggrBgEFBQcBAQRmMGQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0
# # ZS5jb20wPgYIKwYBBQUHMAKGMmh0dHA6Ly93d3cudGhhd3RlLmNvbS9yZXBvc2l0
# # b3J5L1RoYXd0ZV9TR0NfQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEF
# # BQADgYEAsRwpLg1dgCR1gYDK185MFGukXMeQFUvhGqF8eT/CjpdvezyKVuz84gSu
# # 6ccMXgcPQZGQN/F4Xug+Q01eccJjRSVfdvR5qwpqCj+6BFl5oiKDBsveSkrmL5dz
# # s2bn7TdTSYKcLeBkjXxDLHGBqLJ6TNCJ3c4/cbbG5JhGvoema94=
# # -----END CERTIFICATE-----
#
# You can do whatever you want with the certificate String, such as load it
# as a certificate object using the OpenSSL library, and check its fields.
#
# @return [String] the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509), in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail),
# if TLS is active on the connection
#
# @see Connection#start_tls
# @see Connection#ssl_handshake_completed
def get_peer_cert
EventMachine::get_peer_cert @signature
end
def get_cipher_bits
EventMachine::get_cipher_bits @signature
end
def get_cipher_name
EventMachine::get_cipher_name @signature
end
def get_cipher_protocol
EventMachine::get_cipher_protocol @signature
end
def get_sni_hostname
EventMachine::get_sni_hostname @signature
end
# Sends UDP messages.
#
# This method may be called from any Connection object that refers
# to an open datagram socket (see EventMachine#open_datagram_socket).
# The method sends a UDP (datagram) packet containing the data you specify,
# to a remote peer specified by the IP address and port that you give
# as parameters to the method.
# Observe that you may send a zero-length packet (empty string).
# However, you may not send an arbitrarily-large data packet because
# your operating system will enforce a platform-specific limit on
# the size of the outbound packet. (Your kernel
# will respond in a platform-specific way if you send an overlarge
# packet: some will send a truncated packet, some will complain, and
# some will silently drop your request).
# On LANs, it's usually OK to send datagrams up to about 4000 bytes in length,
# but to be really safe, send messages smaller than the Ethernet-packet
# size (typically about 1400 bytes). Some very restrictive WANs
# will either drop or truncate packets larger than about 500 bytes.
#
# @param [String] data Data to send asynchronously
# @param [String] recipient_address IP address of the recipient
# @param [String] recipient_port Port of the recipient
def send_datagram data, recipient_address, recipient_port
data = data.to_s
size = data.bytesize if data.respond_to?(:bytesize)
size ||= data.size
EventMachine::send_datagram @signature, data, size, recipient_address, Integer(recipient_port)
end
# This method is used with stream-connections to obtain the identity
# of the remotely-connected peer. If a peername is available, this method
# returns a sockaddr structure. The method returns nil if no peername is available.
# You can use Socket.unpack_sockaddr_in and its variants to obtain the
# values contained in the peername structure returned from #get_peername.
#
# @example How to get peer IP address and port with EventMachine
#
# require 'socket'
#
# module Handler
# def receive_data data
# port, ip = Socket.unpack_sockaddr_in(get_peername)
# puts "got #{data.inspect} from #{ip}:#{port}"
# end
# end
def get_peername
EventMachine::get_peername @signature
end
# Used with stream-connections to obtain the identity
# of the local side of the connection. If a local name is available, this method
# returns a sockaddr structure. The method returns nil if no local name is available.
# You can use {Socket.unpack_sockaddr_in} and its variants to obtain the
# values contained in the local-name structure returned from this method.
#
# @example
#
# require 'socket'
#
# module Handler
# def receive_data data
# port, ip = Socket.unpack_sockaddr_in(get_sockname)
# puts "got #{data.inspect}"
# end
# end
def get_sockname
EventMachine::get_sockname @signature
end
# Returns the PID (kernel process identifier) of a subprocess
# associated with this Connection object. For use with {EventMachine.popen}
# and similar methods. Returns nil when there is no meaningful subprocess.
#
# @return [Integer]
def get_pid
EventMachine::get_subprocess_pid @signature
end
# Returns a subprocess exit status. Only useful for {EventMachine.popen}. Call it in your
# {#unbind} handler.
#
# @return [Integer]
def get_status
EventMachine::get_subprocess_status @signature
end
# The number of seconds since the last send/receive activity on this connection.
def get_idle_time
EventMachine::get_idle_time @signature
end
# comm_inactivity_timeout returns the current value (float in seconds) of the inactivity-timeout
# property of network-connection and datagram-socket objects. A nonzero value
# indicates that the connection or socket will automatically be closed if no read or write
# activity takes place for at least that number of seconds.
# A zero value (the default) specifies that no automatic timeout will take place.
def comm_inactivity_timeout
EventMachine::get_comm_inactivity_timeout @signature
end
# Allows you to set the inactivity-timeout property for
# a network connection or datagram socket. Specify a non-negative float value in seconds.
# If the value is greater than zero, the connection or socket will automatically be closed
# if no read or write activity takes place for at least that number of seconds.
# Specify a value of zero to indicate that no automatic timeout should take place.
# Zero is the default value.
def comm_inactivity_timeout= value
EventMachine::set_comm_inactivity_timeout @signature, value.to_f
end
alias set_comm_inactivity_timeout comm_inactivity_timeout=
# The duration after which a TCP connection in the connecting state will fail.
# It is important to distinguish this value from {EventMachine::Connection#comm_inactivity_timeout},
# which looks at how long since data was passed on an already established connection.
# The value is a float in seconds.
#
# @return [Float] The duration after which a TCP connection in the connecting state will fail, in seconds.
def pending_connect_timeout
EventMachine::get_pending_connect_timeout @signature
end
# Sets the duration after which a TCP connection in a
# connecting state will fail.
#
# @param [Float, #to_f] value Connection timeout in seconds
def pending_connect_timeout= value
EventMachine::set_pending_connect_timeout @signature, value.to_f
end
alias set_pending_connect_timeout pending_connect_timeout=
# Reconnect to a given host/port with the current instance
#
# @param [String] server Hostname or IP address
# @param [Integer] port Port to reconnect to
def reconnect server, port
EventMachine::reconnect server, port, self
end
# Like {EventMachine::Connection#send_data}, this sends data to the remote end of
# the network connection. {EventMachine::Connection#send_file_data} takes a
# filename as an argument, though, and sends the contents of the file, in one
# chunk.
#
# @param [String] filename Local path of the file to send
#
# @see #send_data
# @author Kirk Haines
def send_file_data filename
EventMachine::send_file_data @signature, filename
end
# Open a file on the filesystem and send it to the remote peer. This returns an
# object of type {EventMachine::Deferrable}. The object's callbacks will be executed
# on the reactor main thread when the file has been completely scheduled for
# transmission to the remote peer. Its errbacks will be called in case of an error (such as file-not-found).
# This method employs various strategies to achieve the fastest possible performance,
# balanced against minimum consumption of memory.
#
# Warning: this feature has an implicit dependency on an outboard extension,
# evma_fastfilereader. You must install this extension in order to use {#stream_file_data}
# with files larger than a certain size (currently 8192 bytes).
#
# @option args [Boolean] :http_chunks (false) If true, this method will stream the file data in a format
# compatible with the HTTP chunked-transfer encoding
#
# @param [String] filename Local path of the file to stream
# @param [Hash] args Options
#
# @return [EventMachine::Deferrable]
def stream_file_data filename, args={}
EventMachine::FileStreamer.new( self, filename, args )
end
# Watches connection for readability. Only possible if the connection was created
# using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler.
#
# @see #notify_readable?
def notify_readable= mode
EventMachine::set_notify_readable @signature, mode
end
# @return [Boolean] true if the connection is being watched for readability.
def notify_readable?
EventMachine::is_notify_readable @signature
end
# Watches connection for writeability. Only possible if the connection was created
# using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler.
#
# @see #notify_writable?
def notify_writable= mode
EventMachine::set_notify_writable @signature, mode
end
# Returns true if the connection is being watched for writability.
def notify_writable?
EventMachine::is_notify_writable @signature
end
# Pause a connection so that {#send_data} and {#receive_data} events are not fired until {#resume} is called.
# @see #resume
def pause
EventMachine::pause_connection @signature
end
# Resume a connection's {#send_data} and {#receive_data} events.
# @see #pause
def resume
EventMachine::resume_connection @signature
end
# @return [Boolean] true if the connect was paused using {EventMachine::Connection#pause}.
# @see #pause
# @see #resume
def paused?
EventMachine::connection_paused? @signature
end
end
end
@@ -0,0 +1,210 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 Jul 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Deferrable
autoload :Pool, 'em/deferrable/pool'
# Specify a block to be executed if and when the Deferrable object receives
# a status of :succeeded. See #set_deferred_status for more information.
#
# Calling this method on a Deferrable object whose status is not yet known
# will cause the callback block to be stored on an internal list.
# If you call this method on a Deferrable whose status is :succeeded, the
# block will be executed immediately, receiving the parameters given to the
# prior #set_deferred_status call.
#
#--
# If there is no status, add a callback to an internal list.
# If status is succeeded, execute the callback immediately.
# If status is failed, do nothing.
#
def callback &block
return unless block
@deferred_status ||= :unknown
if @deferred_status == :succeeded
block.call(*@deferred_args)
elsif @deferred_status != :failed
@callbacks ||= []
@callbacks.unshift block # << block
end
self
end
# Cancels an outstanding callback to &block if any. Undoes the action of #callback.
#
def cancel_callback block
@callbacks ||= []
@callbacks.delete block
end
# Specify a block to be executed if and when the Deferrable object receives
# a status of :failed. See #set_deferred_status for more information.
#--
# If there is no status, add an errback to an internal list.
# If status is failed, execute the errback immediately.
# If status is succeeded, do nothing.
#
def errback &block
return unless block
@deferred_status ||= :unknown
if @deferred_status == :failed
block.call(*@deferred_args)
elsif @deferred_status != :succeeded
@errbacks ||= []
@errbacks.unshift block # << block
end
self
end
# Cancels an outstanding errback to &block if any. Undoes the action of #errback.
#
def cancel_errback block
@errbacks ||= []
@errbacks.delete block
end
# Sets the "disposition" (status) of the Deferrable object. See also the large set of
# sugarings for this method.
# Note that if you call this method without arguments,
# no arguments will be passed to the callback/errback.
# If the user has coded these with arguments, then the
# user code will throw an argument exception.
# Implementors of deferrable classes <b>must</b>
# document the arguments they will supply to user callbacks.
#
# OBSERVE SOMETHING VERY SPECIAL here: you may call this method even
# on the INSIDE of a callback. This is very useful when a previously-registered
# callback wants to change the parameters that will be passed to subsequently-registered
# ones.
#
# You may give either :succeeded or :failed as the status argument.
#
# If you pass :succeeded, then all of the blocks passed to the object using the #callback
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
# passed to the object using #errback will be discarded.
#
# If you pass :failed, then all of the blocks passed to the object using the #errback
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
# passed to the object using # callback will be discarded.
#
# If you pass any arguments to #set_deferred_status in addition to the status argument,
# they will be passed as arguments to any callbacks or errbacks that are executed.
# It's your responsibility to ensure that the argument lists specified in your callbacks and
# errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise
# an ArgumentError.
#
#--
# We're shifting callbacks off and discarding them as we execute them.
# This is valid because by definition callbacks are executed no more than
# once. It also has the magic effect of permitting recursive calls, which
# means that a callback can call #set_deferred_status and change the parameters
# that will be sent to subsequent callbacks down the chain.
#
# Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion
# by Kirk Haines, to work around the memory leak bug that still exists in many Ruby
# versions.
#
# Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of
# handlers. This gets us a little closer to the behavior of Twisted's "deferred,"
# which only allows status to be set once. Prior to making this change, it was possible
# to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it,
# triggering its errbacks! That is clearly undesirable, but it's just as undesirable
# to raise an exception is status is set more than once on a Deferrable. The latter
# behavior would invalidate the idiom of resetting arguments by setting status from
# within a callback or errback, but more seriously it would cause spurious errors
# if a Deferrable was timed out and then an attempt was made to succeed it. See the
# comments under the new method #timeout.
#
def set_deferred_status status, *args
cancel_timeout
@errbacks ||= nil
@callbacks ||= nil
@deferred_status = status
@deferred_args = args
case @deferred_status
when :succeeded
if @callbacks
while cb = @callbacks.pop
cb.call(*@deferred_args)
end
end
@errbacks.clear if @errbacks
when :failed
if @errbacks
while eb = @errbacks.pop
eb.call(*@deferred_args)
end
end
@callbacks.clear if @callbacks
end
end
# Setting a timeout on a Deferrable causes it to go into the failed state after
# the Timeout expires (passing no arguments to the object's errbacks).
# Setting the status at any time prior to a call to the expiration of the timeout
# will cause the timer to be cancelled.
def timeout seconds, *args
cancel_timeout
me = self
@deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)}
self
end
# Cancels an outstanding timeout if any. Undoes the action of #timeout.
#
def cancel_timeout
@deferred_timeout ||= nil
if @deferred_timeout
@deferred_timeout.cancel
@deferred_timeout = nil
end
end
# Sugar for set_deferred_status(:succeeded, ...)
#
def succeed *args
set_deferred_status :succeeded, *args
end
alias set_deferred_success succeed
# Sugar for set_deferred_status(:failed, ...)
#
def fail *args
set_deferred_status :failed, *args
end
alias set_deferred_failure fail
end
# DefaultDeferrable is an otherwise empty class that includes Deferrable.
# This is very useful when you just need to return a Deferrable object
# as a way of communicating deferred status to some other part of a program.
class DefaultDeferrable
include Deferrable
end
end
@@ -0,0 +1,2 @@
warn "EM::Deferrable::Pool is deprecated, please use EM::Pool"
EM::Deferrable::Pool = EM::Pool
@@ -0,0 +1,73 @@
module EventMachine
# Utility class that is useful for file monitoring. Supported events are
#
# * File is modified
# * File is deleted
# * File is moved
#
# @note On Mac OS X, file watching only works when kqueue is enabled
#
# @see EventMachine.watch_file
class FileWatch < Connection
# @private
Cmodified = 'modified'.freeze
# @private
Cdeleted = 'deleted'.freeze
# @private
Cmoved = 'moved'.freeze
# @private
def receive_data(data)
case data
when Cmodified
file_modified
when Cdeleted
file_deleted
when Cmoved
file_moved
end
end
# Returns the path that is being monitored.
#
# @note Current implementation does not pick up on the new filename after a rename occurs.
#
# @return [String]
# @see EventMachine.watch_file
def path
@path
end
# Will be called when the file is modified. Supposed to be redefined by subclasses.
#
# @abstract
def file_modified
end
# Will be called when the file is deleted. Supposed to be redefined by subclasses.
# When the file is deleted, stop_watching will be called after this to make sure everything is
# cleaned up correctly.
#
# @note On Linux (with {http://en.wikipedia.org/wiki/Inotify inotify}), this method will not be called until *all* open file descriptors to
# the file have been closed.
#
# @abstract
def file_deleted
end
# Will be called when the file is moved or renamed. Supposed to be redefined by subclasses.
#
# @abstract
def file_moved
end
# Discontinue monitoring of the file.
#
# This involves cleaning up the underlying monitoring details with kqueue/inotify, and in turn firing {EventMachine::Connection#unbind}.
# This will be called automatically when a file is deleted. User code may call it as well.
def stop_watching
EventMachine::unwatch_filename(@signature)
end # stop_watching
end # FileWatch
end # EventMachine
@@ -0,0 +1,61 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 Jul 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#--
# This defines EventMachine::Deferrable#future, which requires
# that the rest of EventMachine::Deferrable has already been seen.
# (It's in deferrable.rb.)
module EventMachine
module Deferrable
# A future is a sugaring of a typical deferrable usage.
#--
# Evaluate arg (which may be an expression or a block).
# What's the class of arg?
# If arg is an ordinary expression, then return it.
# If arg is deferrable (responds to :set_deferred_status),
# then look at the arguments. If either callback or errback
# are defined, then use them. If neither are defined, then
# use the supplied block (if any) as the callback.
# Then return arg.
def self.future arg, cb=nil, eb=nil, &blk
arg = arg.call if arg.respond_to?(:call)
if arg.respond_to?(:set_deferred_status)
if cb || eb
arg.callback(&cb) if cb
arg.errback(&eb) if eb
else
arg.callback(&blk) if blk
end
end
arg
end
end
end
@@ -0,0 +1,252 @@
module EventMachine
# A simple iterator for concurrent asynchronous work.
#
# Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually,
# instead of happening automatically after the yielded block finishes executing. For example:
#
# (0..10).each{ |num| }
#
# becomes:
#
# EM::Iterator.new(0..10).each{ |num,iter| iter.next }
#
# This is especially useful when doing asynchronous work via reactor libraries and
# functions. For example, given a sync and async http api:
#
# response = sync_http_get(url); ...
# async_http_get(url){ |response| ... }
#
# a synchronous iterator such as:
#
# responses = urls.map{ |url| sync_http_get(url) }
# ...
# puts 'all done!'
#
# could be written as:
#
# EM::Iterator.new(urls).map(proc{ |url,iter|
# async_http_get(url){ |res|
# iter.return(res)
# }
# }, proc{ |responses|
# ...
# puts 'all done!'
# })
#
# Now, you can take advantage of the asynchronous api to issue requests in parallel. For example,
# to fetch 10 urls at a time, simply pass in a concurrency of 10:
#
# EM::Iterator.new(urls, 10).each do |url,iter|
# async_http_get(url){ iter.next }
# end
#
class Iterator
Stop = "EM::Stop"
# Create a new parallel async iterator with specified concurrency.
#
# i = EM::Iterator.new(1..100, 10)
#
# will create an iterator over the range that processes 10 items at a time. Iteration
# is started via #each, #map or #inject
#
# The list may either be an array-like object, or a proc that returns a new object
# to be processed each time it is called. If a proc is used, it must return
# EventMachine::Iterator::Stop to signal the end of the iterations.
#
def initialize(list, concurrency = 1)
raise ArgumentError, 'concurrency must be bigger than zero' unless (concurrency > 0)
if list.respond_to?(:call)
@list = nil
@list_proc = list
elsif list.respond_to?(:to_a)
@list = list.to_a.dup
@list_proc = nil
else
raise ArgumentError, 'argument must be a proc or an array'
end
@concurrency = concurrency
@started = false
@ended = false
end
# Change the concurrency of this iterator. Workers will automatically be spawned or destroyed
# to accomodate the new concurrency level.
#
def concurrency=(val)
old = @concurrency
@concurrency = val
spawn_workers if val > old and @started and !@ended
end
attr_reader :concurrency
# Iterate over a set of items using the specified block or proc.
#
# EM::Iterator.new(1..100).each do |num, iter|
# puts num
# iter.next
# end
#
# An optional second proc is invoked after the iteration is complete.
#
# EM::Iterator.new(1..100).each(
# proc{ |num,iter| iter.next },
# proc{ puts 'all done' }
# )
#
def each(foreach=nil, after=nil, &blk)
raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk
raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended
@started = true
@pending = 0
@workers = 0
all_done = proc{
after.call if after and @ended and @pending == 0
}
@process_next = proc{
# p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list]
unless @ended or @workers > @concurrency
item = next_item()
if item.equal?(Stop)
@ended = true
@workers -= 1
all_done.call
else
@pending += 1
is_done = false
on_done = proc{
raise RuntimeError, 'already completed this iteration' if is_done
is_done = true
@pending -= 1
if @ended
all_done.call
else
EM.next_tick(@process_next)
end
}
class << on_done
alias :next :call
end
foreach.call(item, on_done)
end
else
@workers -= 1
end
}
spawn_workers
self
end
# Collect the results of an asynchronous iteration into an array.
#
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter|
# EM.system(cmd){ |output,status|
# iter.return(output)
# }
# }, proc{ |results|
# p results
# })
#
def map(foreach, after)
index = 0
inject([], proc{ |results,item,iter|
i = index
index += 1
is_done = false
on_done = proc{ |res|
raise RuntimeError, 'already returned a value for this iteration' if is_done
is_done = true
results[i] = res
iter.return(results)
}
class << on_done
alias :return :call
def next
raise NoMethodError, 'must call #return on a map iterator'
end
end
foreach.call(item, on_done)
}, proc{ |results|
after.call(results)
})
end
# Inject the results of an asynchronous iteration onto a given object.
#
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
# EM.system(cmd){ |output,status|
# hash[cmd] = status.exitstatus == 0 ? output.strip : nil
# iter.return(hash)
# }
# }, proc{ |results|
# p results
# })
#
def inject(obj, foreach, after)
each(proc{ |item,iter|
is_done = false
on_done = proc{ |res|
raise RuntimeError, 'already returned a value for this iteration' if is_done
is_done = true
obj = res
iter.next
}
class << on_done
alias :return :call
def next
raise NoMethodError, 'must call #return on an inject iterator'
end
end
foreach.call(obj, item, on_done)
}, proc{
after.call(obj)
})
end
private
# Spawn workers to consume items from the iterator's enumerator based on the current concurrency level.
#
def spawn_workers
EM.next_tick(start_worker = proc{
if @workers < @concurrency and !@ended
# p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended]
@workers += 1
@process_next.call
EM.next_tick(start_worker)
end
})
nil
end
# Return the next item from @list or @list_proc.
# Once items have run out, will return EM::Iterator::Stop. Procs must supply this themselves
def next_item
if @list_proc
@list_proc.call
else
@list.empty? ? Stop : @list.shift
end
end
end
end
# TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next }
# TODO: support iter.pause/resume/stop/break/continue?
# TODO: create some exceptions instead of using RuntimeError
@@ -0,0 +1,66 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 Jul 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
=begin
Message Routing in EventMachine.
The goal here is to enable "routing points," objects that can send and receive
"messages," which are delimited streams of bytes. The boundaries of a message
are preserved as it passes through the reactor system.
There will be several module methods defined in EventMachine to create route-point
objects (which will probably have a base class of EventMachine::MessageRouter
until someone suggests a better name).
As with I/O objects, routing objects will receive events by having the router
core call methods on them. And of course user code can and will define handlers
to deal with events of interest.
The message router base class only really needs a receive_message method. There will
be an EM module-method to send messages, in addition to the module methods to create
the various kinds of message receivers.
The simplest kind of message receiver object can receive messages by being named
explicitly in a parameter to EM#send_message. More sophisticated receivers can define
pub-sub selectors and message-queue names. And they can also define channels for
route-points in other processes or even on other machines.
A message is NOT a marshallable entity. Rather, it's a chunk of flat content more like
an Erlang message. Initially, all content submitted for transmission as a message will
have the to_s method called on it. Eventually, we'll be able to transmit certain structured
data types (XML and YAML documents, Structs within limits) and have them reconstructed
on the other end.
A fundamental goal of the message-routing capability is to interoperate seamlessly with
external systems, including non-Ruby systems like ActiveMQ. We will define various protocol
handlers for things like Stomp and possibly AMQP, but these will be wrapped up and hidden
from the users of the basic routing capability.
As with Erlang, a critical goal is for programs that are built to use message-passing to work
WITHOUT CHANGE when the code is re-based on a multi-process system.
=end
+151
View File
@@ -0,0 +1,151 @@
module EventMachine
# A simple async resource pool based on a resource and work queue. Resources
# are enqueued and work waits for resources to become available.
#
# @example
# require 'em-http-request'
#
# EM.run do
# pool = EM::Pool.new
# spawn = lambda { pool.add EM::HttpRequest.new('http://example.org') }
# 10.times { spawn[] }
# done, scheduled = 0, 0
#
# check = lambda do
# done += 1
# if done >= scheduled
# EM.stop
# end
# end
#
# pool.on_error { |conn| spawn[] }
#
# 100.times do |i|
# scheduled += 1
# pool.perform do |conn|
# req = conn.get :path => '/', :keepalive => true
#
# req.callback do
# p [:success, conn.object_id, i, req.response.size]
# check[]
# end
#
# req.errback { check[] }
#
# req
# end
# end
# end
#
# Resources are expected to be controlled by an object responding to a
# deferrable/completion style API with callback and errback blocks.
#
class Pool
def initialize
@resources = EM::Queue.new
@removed = []
@contents = []
@on_error = nil
end
def add resource
@contents << resource
requeue resource
end
def remove resource
@contents.delete resource
@removed << resource
end
# Returns a list for introspection purposes only. You should *NEVER* call
# modification or work oriented methods on objects in this list. A good
# example use case is periodic statistics collection against a set of
# connection resources.
#
# @example
# pool.contents.inject(0) { |sum, connection| connection.num_bytes }
def contents
@contents.dup
end
# Define a default catch-all for when the deferrables returned by work
# blocks enter a failed state. By default all that happens is that the
# resource is returned to the pool. If on_error is defined, this block is
# responsible for re-adding the resource to the pool if it is still usable.
# In other words, it is generally assumed that on_error blocks explicitly
# handle the rest of the lifetime of the resource.
def on_error *a, &b
@on_error = EM::Callback(*a, &b)
end
# Perform a given #call-able object or block. The callable object will be
# called with a resource from the pool as soon as one is available, and is
# expected to return a deferrable.
#
# The deferrable will have callback and errback added such that when the
# deferrable enters a finished state, the object is returned to the pool.
#
# If on_error is defined, then objects are not automatically returned to the
# pool.
def perform(*a, &b)
work = EM::Callback(*a, &b)
@resources.pop do |resource|
if removed? resource
@removed.delete resource
reschedule work
else
process work, resource
end
end
end
alias reschedule perform
# A peek at the number of enqueued jobs waiting for resources
def num_waiting
@resources.num_waiting
end
# Removed will show resources in a partial pruned state. Resources in the
# removed list may not appear in the contents list if they are currently in
# use.
def removed? resource
@removed.include? resource
end
protected
def requeue resource
@resources.push resource
end
def failure resource
if @on_error
@contents.delete resource
@on_error.call resource
# Prevent users from calling a leak.
@removed.delete resource
else
requeue resource
end
end
def completion deferrable, resource
deferrable.callback { requeue resource }
deferrable.errback { failure resource }
end
def process work, resource
deferrable = work.call resource
if deferrable.kind_of?(EM::Deferrable)
completion deferrable, resource
else
raise ArgumentError, "deferrable expected from work"
end
rescue
failure resource
raise
end
end
end
@@ -0,0 +1,45 @@
module EventMachine
# This is subclassed from EventMachine::Connection for use with the process monitoring API. Read the
# documentation on the instance methods of this class, and for a full explanation see EventMachine.watch_process.
class ProcessWatch < Connection
# @private
Cfork = 'fork'.freeze
# @private
Cexit = 'exit'.freeze
# @private
def receive_data(data)
case data
when Cfork
process_forked
when Cexit
process_exited
end
end
# Returns the pid that EventMachine::watch_process was originally called with.
def pid
@pid
end
# Should be redefined with the user's custom callback that will be fired when the prcess is forked.
#
# There is currently not an easy way to get the pid of the forked child.
def process_forked
end
# Should be redefined with the user's custom callback that will be fired when the process exits.
#
# stop_watching is called automatically after this callback
def process_exited
end
# Discontinue monitoring of the process.
# This will be called automatically when a process dies. User code may call it as well.
def stop_watching
EventMachine::unwatch_pid(@signature)
end
end
end
@@ -0,0 +1,123 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 13 Dec 07
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
# EM::DeferrableChildProcess is a sugaring of a common use-case
# involving EM::popen.
# Call the #open method on EM::DeferrableChildProcess, passing
# a command-string. #open immediately returns an EM::Deferrable
# object. It also schedules the forking of a child process, which
# will execute the command passed to #open.
# When the forked child terminates, the Deferrable will be signalled
# and execute its callbacks, passing the data that the child process
# wrote to stdout.
#
class DeferrableChildProcess < EventMachine::Connection
include EventMachine::Deferrable
# @private
def initialize
super
@data = []
end
# Sugars a common use-case involving forked child processes.
# #open takes a String argument containing an shell command
# string (including arguments if desired). #open immediately
# returns an EventMachine::Deferrable object, without blocking.
#
# It also invokes EventMachine#popen to run the passed-in
# command in a forked child process.
#
# When the forked child terminates, the Deferrable that
# #open calls its callbacks, passing the data returned
# from the child process.
#
def self.open cmd
EventMachine.popen( cmd, DeferrableChildProcess )
end
# @private
def receive_data data
@data << data
end
# @private
def unbind
succeed( @data.join )
end
end
# @private
class SystemCmd < EventMachine::Connection
def initialize cb
@cb = cb
@output = []
end
def receive_data data
@output << data
end
def unbind
@cb.call @output.join(''), get_status if @cb
end
end
# EM::system is a simple wrapper for EM::popen. It is similar to Kernel::system, but requires a
# single string argument for the command and performs no shell expansion.
#
# The block or proc passed to EM::system is called with two arguments: the output generated by the command,
# and a Process::Status that contains information about the command's execution.
#
# EM.run{
# EM.system('ls'){ |output,status| puts output if status.exitstatus == 0 }
# }
#
# You can also supply an additional proc to send some data to the process:
#
# EM.run{
# EM.system('sh', proc{ |process|
# process.send_data("echo hello\n")
# process.send_data("exit\n")
# }, proc{ |out,status|
# puts(out)
# })
# }
#
# Like EventMachine.popen, EventMachine.system currently does not work on windows.
# It returns the pid of the spawned process.
def EventMachine::system cmd, *args, &cb
cb ||= args.pop if args.last.is_a? Proc
init = args.pop if args.last.is_a? Proc
# merge remaining arguments into the command
cmd = [cmd, *args] if args.any?
EM.get_subprocess_pid(EM.popen(cmd, SystemCmd, cb) do |c|
init[c] if init
end.signature)
end
end
@@ -0,0 +1,37 @@
module EventMachine
# This module contains various protocol implementations, including:
# - HttpClient and HttpClient2
# - Stomp
# - Memcache
# - SmtpClient and SmtpServer
# - SASLauth and SASLauthclient
# - LineProtocol, LineAndTextProtocol and LineText2
# - HeaderAndContentProtocol
# - Postgres3
# - ObjectProtocol
#
# The protocol implementations live in separate files in the protocols/ subdirectory,
# but are auto-loaded when they are first referenced in your application.
#
# EventMachine::Protocols is also aliased to EM::P for easier usage.
#
module Protocols
# TODO : various autotools are completely useless with the lack of naming
# convention, we need to correct that!
autoload :TcpConnectTester, 'em/protocols/tcptest'
autoload :HttpClient, 'em/protocols/httpclient'
autoload :HttpClient2, 'em/protocols/httpclient2'
autoload :LineAndTextProtocol, 'em/protocols/line_and_text'
autoload :HeaderAndContentProtocol, 'em/protocols/header_and_content'
autoload :LineText2, 'em/protocols/linetext2'
autoload :Stomp, 'em/protocols/stomp'
autoload :SmtpClient, 'em/protocols/smtpclient'
autoload :SmtpServer, 'em/protocols/smtpserver'
autoload :SASLauth, 'em/protocols/saslauth'
autoload :Memcache, 'em/protocols/memcache'
autoload :Postgres3, 'em/protocols/postgres3'
autoload :ObjectProtocol, 'em/protocols/object_protocol'
autoload :Socks4, 'em/protocols/socks4'
autoload :LineProtocol, 'em/protocols/line_protocol'
end
end
@@ -0,0 +1,138 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 Nov 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Protocols
# === Usage
#
# class RequestHandler < EM::P::HeaderAndContentProtocol
# def receive_request headers, content
# p [:request, headers, content]
# end
# end
#
# EM.run{
# EM.start_server 'localhost', 80, RequestHandler
# }
#
#--
# Originally, this subclassed LineAndTextProtocol, which in
# turn relies on BufferedTokenizer, which doesn't gracefully
# handle the transitions between lines and binary text.
# Changed 13Sep08 by FCianfrocca.
class HeaderAndContentProtocol < Connection
include LineText2
ContentLengthPattern = /Content-length:\s*(\d+)/i
def initialize *args
super
init_for_request
end
def receive_line line
case @hc_mode
when :discard_blanks
unless line == ""
@hc_mode = :headers
receive_line line
end
when :headers
if line == ""
raise "unrecognized state" unless @hc_headers.length > 0
if respond_to?(:receive_headers)
receive_headers @hc_headers
end
# @hc_content_length will be nil, not 0, if there was no content-length header.
if @hc_content_length.to_i > 0
set_binary_mode @hc_content_length
else
dispatch_request
end
else
@hc_headers << line
if ContentLengthPattern =~ line
# There are some attacks that rely on sending multiple content-length
# headers. This is a crude protection, but needs to become tunable.
raise "extraneous content-length header" if @hc_content_length
@hc_content_length = $1.to_i
end
if @hc_headers.length == 1 and respond_to?(:receive_first_header_line)
receive_first_header_line line
end
end
else
raise "internal error, unsupported mode"
end
end
def receive_binary_data text
@hc_content = text
dispatch_request
end
def dispatch_request
if respond_to?(:receive_request)
receive_request @hc_headers, @hc_content
end
init_for_request
end
private :dispatch_request
def init_for_request
@hc_mode = :discard_blanks
@hc_headers = []
# originally was @hc_headers ||= []; @hc_headers.clear to get a performance
# boost, but it's counterproductive because a subclassed handler will have to
# call dup to use the header array we pass in receive_headers.
@hc_content_length = nil
@hc_content = ""
end
private :init_for_request
# Basically a convenience method. We might create a subclass that does this
# automatically. But it's such a performance killer.
def headers_2_hash hdrs
self.class.headers_2_hash hdrs
end
class << self
def headers_2_hash hdrs
hash = {}
hdrs.each {|h|
if /\A([^\s:]+)\s*:\s*/ =~ h
tail = $'.dup
hash[ $1.downcase.gsub(/-/,"_").intern ] = tail
end
}
hash
end
end
end
end
end
@@ -0,0 +1,300 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 July 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Protocols
# <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead.
#
# @example
# EventMachine.run {
# http = EventMachine::Protocols::HttpClient.request(
# :host => server,
# :port => 80,
# :request => "/index.html",
# :query_string => "parm1=value1&parm2=value2"
# )
# http.callback {|response|
# puts response[:status]
# puts response[:headers]
# puts response[:content]
# }
# }
#--
# TODO:
# Add streaming so we can support enormous POSTs. Current max is 20meg.
# Timeout for connections that run too long or hang somewhere in the middle.
# Persistent connections (HTTP/1.1), may need a associated delegate object.
# DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
# DNS lookups are unbelievably slow.
# HEAD requests.
# Convenience methods for requests. get, post, url, etc.
# SSL.
# Handle status codes like 304, 100, etc.
# Refactor this code so that protocol errors all get handled one way (an exception?),
# instead of sprinkling set_deferred_status :failed calls everywhere.
class HttpClient < Connection
include EventMachine::Deferrable
MaxPostContentLength = 20 * 1024 * 1024
def initialize
warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead."
@connected = false
end
# @param args [Hash] The request arguments
# @option args [String] :host The host IP/DNS name
# @option args [Integer] :port The port to connect too
# @option args [String] :verb The request type [GET | POST | DELETE | PUT]
# @option args [String] :request The request path
# @option args [Hash] :basic_auth The basic auth credentials (:username and :password)
# @option args [String] :content The request content
# @option args [String] :contenttype The content type (e.g. text/plain)
# @option args [String] :query_string The query string
# @option args [String] :host_header The host header to set
# @option args [String] :cookie Cookies to set
def self.request( args = {} )
args[:port] ||= 80
EventMachine.connect( args[:host], args[:port], self ) {|c|
# According to the docs, we will get here AFTER post_init is called.
c.instance_eval {@args = args}
}
end
def post_init
@start_time = Time.now
@data = ""
@read_state = :base
end
# We send the request when we get a connection.
# AND, we set an instance variable to indicate we passed through here.
# That allows #unbind to know whether there was a successful connection.
# NB: This naive technique won't work when we have to support multiple
# requests on a single connection.
def connection_completed
@connected = true
send_request @args
end
def send_request args
args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
verb = args[:verb].to_s.upcase
unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
return # NOTE THE EARLY RETURN, we're not sending any data.
end
request = args[:request] || "/"
unless request[0,1] == "/"
request = "/" + request
end
qs = args[:query_string] || ""
if qs.length > 0 and qs[0,1] != '?'
qs = "?" + qs
end
version = args[:version] || "1.1"
# Allow an override for the host header if it's not the connect-string.
host = args[:host_header] || args[:host] || "_"
# For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
port = args[:port].to_i != 80 ? ":#{args[:port]}" : ""
# POST items.
postcontenttype = args[:contenttype] || "application/octet-stream"
postcontent = args[:content] || ""
raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
# ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
# TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
req = [
"#{verb} #{request}#{qs} HTTP/#{version}",
"Host: #{host}#{port}",
"User-agent: Ruby EventMachine",
]
if verb == "POST" || verb == "PUT"
req << "Content-type: #{postcontenttype}"
req << "Content-length: #{postcontent.length}"
end
# TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
# Eventually we will want to deal intelligently with arrays and hashes.
if args[:cookie]
req << "Cookie: #{args[:cookie]}"
end
# Allow custom HTTP headers, e.g. SOAPAction
args[:custom_headers].each do |k,v|
req << "#{k}: #{v}"
end if args[:custom_headers]
# Basic-auth stanza contributed by Matt Murphy.
if args[:basic_auth]
basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
req << "Authorization: Basic #{basic_auth_string}"
end
req << ""
reqstring = req.map {|l| "#{l}\r\n"}.join
send_data reqstring
if verb == "POST" || verb == "PUT"
send_data postcontent
end
end
def receive_data data
while data and data.length > 0
case @read_state
when :base
# Perform any per-request initialization here and don't consume any data.
@data = ""
@headers = []
@content_length = nil # not zero
@content = ""
@status = nil
@chunked = false
@chunk_length = nil
@read_state = :header
@connection_close = nil
when :header
ary = data.split( /\r?\n/m, 2 )
if ary.length == 2
data = ary.last
if ary.first == ""
if (@content_length and @content_length > 0) || @chunked || @connection_close
@read_state = :content
else
dispatch_response
@read_state = :base
end
else
@headers << ary.first
if @headers.length == 1
parse_response_line
elsif ary.first =~ /\Acontent-length:\s*/i
# Only take the FIRST content-length header that appears,
# which we can distinguish because @content_length is nil.
# TODO, it's actually a fatal error if there is more than one
# content-length header, because the caller is presumptively
# a bad guy. (There is an exploit that depends on multiple
# content-length headers.)
@content_length ||= $'.to_i
elsif ary.first =~ /\Aconnection:\s*close/i
@connection_close = true
elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i
@chunked = true
end
end
else
@data << data
data = ""
end
when :content
if @chunked && @chunk_length
bytes_needed = @chunk_length - @chunk_read
new_data = data[0, bytes_needed]
@chunk_read += new_data.length
@content += new_data
data = data[bytes_needed..-1] || ""
if @chunk_length == @chunk_read && data[0,2] == "\r\n"
@chunk_length = nil
data = data[2..-1]
end
elsif @chunked
if (m = data.match(/\A(\S*)\r\n/m))
data = data[m[0].length..-1]
@chunk_length = m[1].to_i(16)
@chunk_read = 0
if @chunk_length == 0
dispatch_response
@read_state = :base
end
end
elsif @content_length
# If there was no content-length header, we have to wait until the connection
# closes. Everything we get until that point is content.
# TODO: Must impose a content-size limit, and also must implement chunking.
# Also, must support either temporary files for large content, or calling
# a content-consumer block supplied by the user.
bytes_needed = @content_length - @content.length
@content += data[0, bytes_needed]
data = data[bytes_needed..-1] || ""
if @content_length == @content.length
dispatch_response
@read_state = :base
end
else
@content << data
data = ""
end
end
end
end
# We get called here when we have received an HTTP response line.
# It's an opportunity to throw an exception or trigger other exceptional
# handling.
def parse_response_line
if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
@status = $1.to_i
else
set_deferred_status :failed, {
:status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
}
close_connection
end
end
private :parse_response_line
def dispatch_response
@read_state = :base
set_deferred_status :succeeded, {
:content => @content,
:headers => @headers,
:status => @status
}
# TODO, we close the connection for now, but this is wrong for persistent clients.
close_connection
end
def unbind
if !@connected
set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
elsif (@read_state == :content and @content_length == nil)
dispatch_response
end
end
end
end
end
@@ -0,0 +1,600 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 July 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Protocols
# <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead.
#
# === Usage
#
# EM.run{
# conn = EM::Protocols::HttpClient2.connect 'google.com', 80
#
# req = conn.get('/')
# req.callback{ |response|
# p(response.status)
# p(response.headers)
# p(response.content)
# }
# }
class HttpClient2 < Connection
include LineText2
def initialize
warn "HttpClient2 is deprecated and will be removed. EM-Http-Request should be used instead."
@authorization = nil
@closed = nil
@requests = nil
end
# @private
class Request
include Deferrable
attr_reader :version
attr_reader :status
attr_reader :header_lines
attr_reader :headers
attr_reader :content
attr_reader :internal_error
def initialize conn, args
@conn = conn
@args = args
@header_lines = []
@headers = {}
@blanks = 0
@chunk_trailer = nil
@chunking = nil
end
def send_request
az = @args[:authorization] and az = "Authorization: #{az}\r\n"
r = [
"#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n",
"Host: #{@args[:host_header] || "_"}\r\n",
az || "",
"\r\n"
]
@conn.send_data r.join
end
#--
#
def receive_line ln
if @chunk_trailer
receive_chunk_trailer(ln)
elsif @chunking
receive_chunk_header(ln)
else
receive_header_line(ln)
end
end
#--
#
def receive_chunk_trailer ln
if ln.length == 0
@conn.pop_request
succeed(self)
else
p "Received chunk trailer line"
end
end
#--
# Allow up to ten blank lines before we get a real response line.
# Allow no more than 100 lines in the header.
#
def receive_header_line ln
if ln.length == 0
if @header_lines.length > 0
process_header
else
@blanks += 1
if @blanks > 10
@conn.close_connection
end
end
else
@header_lines << ln
if @header_lines.length > 100
@internal_error = :bad_header
@conn.close_connection
end
end
end
#--
# Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks.
#
def receive_chunk_header ln
if ln.length > 0
chunksize = ln.to_i(16)
if chunksize > 0
@conn.set_text_mode(ln.to_i(16))
else
@content = @content ? @content.join : ''
@chunk_trailer = true
end
else
# We correctly come here after each chunk gets read.
# p "Got A BLANK chunk line"
end
end
#--
# We get a single chunk. Append it to the incoming content and switch back to line mode.
#
def receive_chunked_text text
# p "RECEIVED #{text.length} CHUNK"
(@content ||= []) << text
end
#--
# TODO, inefficient how we're handling this. Part of it is done so as to
# make sure we don't have problems in detecting chunked-encoding, content-length,
# etc.
#
HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
ClenRE = /\AContent-length:\s*(\d+)/i
ChunkedRE = /\ATransfer-encoding:\s*chunked/i
ColonRE = /\:\s*/
def process_header
unless @header_lines.first =~ HttpResponseRE
@conn.close_connection
@internal_error = :bad_request
end
@version = $1.dup
@status = $2.dup.to_i
clen = nil
chunks = nil
@header_lines.each_with_index do |e,ix|
if ix > 0
hdr,val = e.split(ColonRE,2)
(@headers[hdr.downcase] ||= []) << val
end
if clen == nil and e =~ ClenRE
clen = $1.dup.to_i
end
if e =~ ChunkedRE
chunks = true
end
end
if clen
# If the content length is zero we should not call set_text_mode,
# because a value of zero will make it wait forever, hanging the
# connection. Just return success instead, with empty content.
if clen == 0 then
@content = ""
@conn.pop_request
succeed(self)
else
@conn.set_text_mode clen
end
elsif chunks
@chunking = true
else
# Chunked transfer, multipart, or end-of-connection.
# For end-of-connection, we need to go the unbind
# method and suppress its desire to fail us.
p "NO CLEN"
p @args[:uri]
p @header_lines
@internal_error = :unsupported_clen
@conn.close_connection
end
end
private :process_header
def receive_text text
@chunking ? receive_chunked_text(text) : receive_sized_text(text)
end
#--
# At the present time, we only handle contents that have a length
# specified by the content-length header.
#
def receive_sized_text text
@content = text
@conn.pop_request
succeed(self)
end
end
# Make a connection to a remote HTTP server.
# Can take either a pair of arguments (which will be interpreted as
# a hostname/ip-address and a port), or a hash.
# If the arguments are a hash, then supported values include:
# :host => a hostname or ip-address
# :port => a port number
# :ssl => true to enable ssl
def self.connect *args
if args.length == 2
args = {:host=>args[0], :port=>args[1]}
else
args = args.first
end
h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl])
conn = EM.connect( h, prt, self )
conn.start_tls if ssl
conn.set_default_host_header( h, prt, ssl )
conn
end
# Get a url
#
# req = conn.get(:uri => '/')
# req.callback{|response| puts response.content }
#
def get args
if args.is_a?(String)
args = {:uri=>args}
end
args[:verb] = "GET"
request args
end
# Post to a url
#
# req = conn.post('/data')
# req.callback{|response| puts response.content }
#--
# XXX there's no way to supply a POST body.. wtf?
def post args
if args.is_a?(String)
args = {:uri=>args}
end
args[:verb] = "POST"
request args
end
#--
# Compute and remember a string to be used as the host header in HTTP requests
# unless the user overrides it with an argument to #request.
#
# @private
def set_default_host_header host, port, ssl
if (ssl and port != 443) or (!ssl and port != 80)
@host_header = "#{host}:#{port}"
else
@host_header = host
end
end
# @private
def post_init
super
@connected = EM::DefaultDeferrable.new
end
# @private
def connection_completed
super
@connected.succeed
end
#--
# All pending requests, if any, must fail.
# We might come here without ever passing through connection_completed
# in case we can't connect to the server. We'll also get here when the
# connection closes (either because the server closes it, or we close it
# due to detecting an internal error or security violation).
# In either case, run down all pending requests, if any, and signal failure
# on them.
#
# Set and remember a flag (@closed) so we can immediately fail any
# subsequent requests.
#
# @private
def unbind
super
@closed = true
(@requests || []).each {|r| r.fail}
end
# @private
def request args
args[:host_header] = @host_header unless args.has_key?(:host_header)
args[:authorization] = @authorization unless args.has_key?(:authorization)
r = Request.new self, args
if @closed
r.fail
else
(@requests ||= []).unshift r
@connected.callback {r.send_request}
end
r
end
# @private
def receive_line ln
if req = @requests.last
req.receive_line ln
else
p "??????????"
p ln
end
end
# @private
def receive_binary_data text
@requests.last.receive_text text
end
#--
# Called by a Request object when it completes.
#
# @private
def pop_request
@requests.pop
end
end
=begin
class HttpClient2x < Connection
include LineText2
# TODO: Make this behave appropriate in case a #connect fails.
# Currently, this produces no errors.
# Make a connection to a remote HTTP server.
# Can take either a pair of arguments (which will be interpreted as
# a hostname/ip-address and a port), or a hash.
# If the arguments are a hash, then supported values include:
# :host => a hostname or ip-address;
# :port => a port number
#--
# TODO, support optional encryption arguments like :ssl
def self.connect *args
if args.length == 2
args = {:host=>args[0], :port=>args[1]}
else
args = args.first
end
h,prt = args[:host],Integer(args[:port])
EM.connect( h, prt, self, h, prt )
end
#--
# Sugars a connection that makes a single request and then
# closes the connection. Matches the behavior and the arguments
# of the original implementation of class HttpClient.
#
# Intended primarily for back compatibility, but the idiom
# is probably useful so it's not deprecated.
# We return a Deferrable, as did the original implementation.
#
# Because we're improving the way we deal with errors and exceptions
# (specifically, HTTP response codes other than 2xx will trigger the
# errback rather than the callback), this may break some existing code.
#
def self.request args
c = connect args
end
#--
# Requests can be pipelined. When we get a request, add it to the
# front of a queue as an array. The last element of the @requests
# array is always the oldest request received. Each element of the
# @requests array is a two-element array consisting of a hash with
# the original caller's arguments, and an initially-empty Ostruct
# containing the data we retrieve from the server's response.
# Maintain the instance variable @current_response, which is the response
# of the oldest pending request. That's just to make other code a little
# easier. If the variable doesn't exist when we come here, we're
# obviously the first request being made on the connection.
#
# The reason for keeping this method private (and requiring use of the
# convenience methods #get, #post, #head, etc) is to avoid the small
# performance penalty of canonicalizing the verb.
#
def request args
d = EventMachine::DefaultDeferrable.new
if @closed
d.fail
return d
end
o = OpenStruct.new
o.deferrable = d
(@requests ||= []).unshift [args, o]
@current_response ||= @requests.last.last
@connected.callback {
az = args[:authorization] and az = "Authorization: #{az}\r\n"
r = [
"#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n",
"Host: #{args[:host_header] || @host_header}\r\n",
az || "",
"\r\n"
]
p r
send_data r.join
}
o.deferrable
end
private :request
def get args
if args.is_a?(String)
args = {:uri=>args}
end
args[:verb] = "GET"
request args
end
def initialize host, port
super
@host_header = "#{host}:#{port}"
end
def post_init
super
@connected = EM::DefaultDeferrable.new
end
def connection_completed
super
@connected.succeed
end
#--
# Make sure to throw away any leftover incoming data if we've
# been closed due to recognizing an error.
#
# Generate an internal error if we get an unreasonable number of
# header lines. It could be malicious.
#
def receive_line ln
p ln
return if @closed
if ln.length > 0
(@current_response.headers ||= []).push ln
abort_connection if @current_response.headers.length > 100
else
process_received_headers
end
end
#--
# We come here when we've seen all the headers for a particular request.
# What we do next depends on the response line (which should be the
# first line in the header set), and whether there is content to read.
# We may transition into a text-reading state to read content, or
# we may abort the connection, or we may go right back into parsing
# responses for the next response in the chain.
#
# We make an ASSUMPTION that the first line is an HTTP response.
# Anything else produces an error that aborts the connection.
# This may not be enough, because it may be that responses to pipelined
# requests will come with a blank-line delimiter.
#
# Any non-2xx response will be treated as a fatal error, and abort the
# connection. We will set up the status and other response parameters.
# TODO: we will want to properly support 1xx responses, which some versions
# of IIS copiously generate.
# TODO: We need to give the option of not aborting the connection with certain
# non-200 responses, in order to work with NTLM and other authentication
# schemes that work at the level of individual connections.
#
# Some error responses will get sugarings. For example, we'll return the
# Location header in the response in case of a 301/302 response.
#
# Possible dispositions here:
# 1) No content to read (either content-length is zero or it's a HEAD request);
# 2) Switch to text mode to read a specific number of bytes;
# 3) Read a chunked or multipart response;
# 4) Read till the server closes the connection.
#
# Our reponse to the client can be either to wait till all the content
# has been read and then to signal caller's deferrable, or else to signal
# it when we finish the processing the headers and then expect the caller
# to have given us a block to call as the content comes in. And of course
# the latter gets stickier with chunks and multiparts.
#
HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
ClenRE = /\AContent-length:\s*(\d+)/i
def process_received_headers
abort_connection unless @current_response.headers.first =~ HttpResponseRE
@current_response.version = $1.dup
st = $2.dup
@current_response.status = st.to_i
abort_connection unless st[0,1] == "2"
clen = nil
@current_response.headers.each do |e|
if clen == nil and e =~ ClenRE
clen = $1.dup.to_i
end
end
if clen
set_text_mode clen
end
end
private :process_received_headers
def receive_binary_data text
@current_response.content = text
@current_response.deferrable.succeed @current_response
@requests.pop
@current_response = (@requests.last || []).last
set_line_mode
end
# We've received either a server error or an internal error.
# Close the connection and abort any pending requests.
#--
# When should we call close_connection? It will cause #unbind
# to be fired. Should the user expect to see #unbind before
# we call #receive_http_error, or the other way around?
#
# Set instance variable @closed. That's used to inhibit further
# processing of any inbound data after an error has been recognized.
#
# We shouldn't have to worry about any leftover outbound data,
# because we call close_connection (not close_connection_after_writing).
# That ensures that any pipelined requests received after an error
# DO NOT get streamed out to the server on this connection.
# Very important. TODO, write a unit-test to establish that behavior.
#
def abort_connection
close_connection
@closed = true
@current_response.deferrable.fail( @current_response )
end
#------------------------
# Below here are user-overridable methods.
end
=end
end
end
@@ -0,0 +1,125 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 November 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
module EventMachine
module Protocols
# A protocol that handles line-oriented data with interspersed binary text.
#
# This version is optimized for performance. See EventMachine::Protocols::LineText2
# for a version which is optimized for correctness with regard to binary text blocks
# that can switch back to line mode.
class LineAndTextProtocol < Connection
MaxBinaryLength = 32*1024*1024
def initialize *args
super
lbp_init_line_state
end
def receive_data data
if @lbp_mode == :lines
begin
@lpb_buffer.extract(data).each do |line|
receive_line(line.chomp) if respond_to?(:receive_line)
end
rescue
receive_error('overlength line') if respond_to?(:receive_error)
close_connection
return
end
else
if @lbp_binary_limit > 0
wanted = @lbp_binary_limit - @lbp_binary_bytes_received
chunk = nil
if data.length > wanted
chunk = data.slice!(0...wanted)
else
chunk = data
data = ""
end
@lbp_binary_buffer[@lbp_binary_bytes_received...(@lbp_binary_bytes_received+chunk.length)] = chunk
@lbp_binary_bytes_received += chunk.length
if @lbp_binary_bytes_received == @lbp_binary_limit
receive_binary_data(@lbp_binary_buffer) if respond_to?(:receive_binary_data)
lbp_init_line_state
end
receive_data(data) if data.length > 0
else
receive_binary_data(data) if respond_to?(:receive_binary_data)
data = ""
end
end
end
def unbind
if @lbp_mode == :binary and @lbp_binary_limit > 0
if respond_to?(:receive_binary_data)
receive_binary_data( @lbp_binary_buffer[0...@lbp_binary_bytes_received] )
end
end
end
# Set up to read the supplied number of binary bytes.
# This recycles all the data currently waiting in the line buffer, if any.
# If the limit is nil, then ALL subsequent data will be treated as binary
# data and passed to the upstream protocol handler as we receive it.
# If a limit is given, we'll hold the incoming binary data and not
# pass it upstream until we've seen it all, or until there is an unbind
# (in which case we'll pass up a partial).
# Specifying nil for the limit (the default) means there is no limit.
# Specifiyng zero for the limit will cause an immediate transition back to line mode.
#
def set_binary_mode size = nil
if @lbp_mode == :lines
if size == 0
receive_binary_data("") if respond_to?(:receive_binary_data)
# Do no more work here. Stay in line mode and keep consuming data.
else
@lbp_binary_limit = size.to_i # (nil will be stored as zero)
if @lbp_binary_limit > 0
raise "Overlength" if @lbp_binary_limit > MaxBinaryLength # arbitrary sanity check
@lbp_binary_buffer = "\0" * @lbp_binary_limit
@lbp_binary_bytes_received = 0
end
@lbp_mode = :binary
receive_data @lpb_buffer.flush
end
else
raise "invalid operation"
end
end
#--
# For internal use, establish protocol baseline for handling lines.
def lbp_init_line_state
@lpb_buffer = BufferedTokenizer.new("\n")
@lbp_mode = :lines
end
private :lbp_init_line_state
end
end
end
@@ -0,0 +1,29 @@
module EventMachine
module Protocols
# LineProtocol will parse out newline terminated strings from a receive_data stream
#
# module Server
# include EM::P::LineProtocol
#
# def receive_line(line)
# send_data("you said: #{line}")
# end
# end
#
module LineProtocol
# @private
def receive_data data
(@buf ||= '') << data
while @buf.slice!(/(.*?)\r?\n/)
receive_line($1)
end
end
# Invoked with lines received over the network
def receive_line(line)
# stub
end
end
end
end
@@ -0,0 +1,179 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 November 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Protocols
# In the grand, time-honored tradition of re-inventing the wheel, we offer
# here YET ANOTHER protocol that handles line-oriented data with interspersed
# binary text. This one trades away some of the performance optimizations of
# EventMachine::Protocols::LineAndTextProtocol in order to get better correctness
# with regard to binary text blocks that can switch back to line mode. It also
# permits the line-delimiter to change in midstream.
# This was originally written to support Stomp.
module LineText2
# TODO! We're not enforcing the limits on header lengths and text-lengths.
# When we get around to that, call #receive_error if the user defined it, otherwise
# throw exceptions.
MaxBinaryLength = 32*1024*1024
#--
# Will loop internally until there's no data left to read.
# That way the user-defined handlers we call can modify the
# handling characteristics on a per-token basis.
#
def receive_data data
return unless (data and data.length > 0)
# Do this stuff in lieu of a constructor.
@lt2_mode ||= :lines
@lt2_delimiter ||= "\n"
@lt2_linebuffer ||= []
remaining_data = data
while remaining_data.length > 0
if @lt2_mode == :lines
delimiter_string = case @lt2_delimiter
when Regexp
remaining_data.slice(@lt2_delimiter)
else
@lt2_delimiter
end
ix = remaining_data.index(delimiter_string) if delimiter_string
if ix
@lt2_linebuffer << remaining_data[0...ix]
ln = @lt2_linebuffer.join
@lt2_linebuffer.clear
if @lt2_delimiter == "\n"
ln.chomp!
end
receive_line ln
remaining_data = remaining_data[(ix+delimiter_string.length)..-1]
else
@lt2_linebuffer << remaining_data
remaining_data = ""
end
elsif @lt2_mode == :text
if @lt2_textsize
needed = @lt2_textsize - @lt2_textpos
will_take = if remaining_data.length > needed
needed
else
remaining_data.length
end
@lt2_textbuffer << remaining_data[0...will_take]
tail = remaining_data[will_take..-1]
@lt2_textpos += will_take
if @lt2_textpos >= @lt2_textsize
# Reset line mode (the default behavior) BEFORE calling the
# receive_binary_data. This makes it possible for user code
# to call set_text_mode, enabling chains of text blocks
# (which can possibly be of different sizes).
set_line_mode
receive_binary_data @lt2_textbuffer.join
receive_end_of_binary_data
end
remaining_data = tail
else
receive_binary_data remaining_data
remaining_data = ""
end
end
end
end
# The line delimiter may be a regular expression or a string. Anything
# passed to set_delimiter other than a regular expression will be
# converted to a string.
def set_delimiter delim
@lt2_delimiter = case delim
when Regexp
delim
else
delim.to_s
end
end
# Called internally but also exposed to user code, for the case in which
# processing of binary data creates a need to transition back to line mode.
# We support an optional parameter to "throw back" some data, which might
# be an umprocessed chunk of the transmitted binary data, or something else
# entirely.
def set_line_mode data=""
@lt2_mode = :lines
(@lt2_linebuffer ||= []).clear
receive_data data.to_s
end
def set_text_mode size=nil
if size == 0
set_line_mode
else
@lt2_mode = :text
(@lt2_textbuffer ||= []).clear
@lt2_textsize = size # which can be nil, signifying no limit
@lt2_textpos = 0
end
end
# Alias for #set_text_mode, added for back-compatibility with LineAndTextProtocol.
def set_binary_mode size=nil
set_text_mode size
end
# In case of a dropped connection, we'll send a partial buffer to user code
# when in sized text mode. User overrides of #receive_binary_data need to
# be aware that they may get a short buffer.
def unbind
@lt2_mode ||= nil
if @lt2_mode == :text and @lt2_textpos > 0
receive_binary_data @lt2_textbuffer.join
end
end
# Stub. Should be subclassed by user code.
def receive_line ln
# no-op
end
# Stub. Should be subclassed by user code.
def receive_binary_data data
# no-op
end
# Stub. Should be subclassed by user code.
# This is called when transitioning internally from text mode
# back to line mode. Useful when client code doesn't want
# to keep track of how much data it's received.
def receive_end_of_binary_data
# no-op
end
end
end
end
@@ -0,0 +1,331 @@
module EventMachine
module Protocols
# Implements the Memcache protocol (http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt).
# Requires memcached >= 1.2.4 w/ noreply support
#
# == Usage example
#
# EM.run{
# cache = EM::P::Memcache.connect 'localhost', 11211
#
# cache.set :a, 'hello'
# cache.set :b, 'hi'
# cache.set :c, 'how are you?'
# cache.set :d, ''
#
# cache.get(:a){ |v| p v }
# cache.get_hash(:a, :b, :c, :d){ |v| p v }
# cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] }
#
# cache.get(:a,:z,:b,:y,:d){ |a,z,b,y,d| p [a,z,b,y,d] }
#
# cache.get(:missing){ |m| p [:missing=, m] }
# cache.set(:missing, 'abc'){ p :stored }
# cache.get(:missing){ |m| p [:missing=, m] }
# cache.del(:missing){ p :deleted }
# cache.get(:missing){ |m| p [:missing=, m] }
# }
#
module Memcache
include EM::Deferrable
##
# constants
unless defined? Cempty
# @private
Cstored = 'STORED'.freeze
# @private
Cend = 'END'.freeze
# @private
Cdeleted = 'DELETED'.freeze
# @private
Cunknown = 'NOT_FOUND'.freeze
# @private
Cerror = 'ERROR'.freeze
# @private
Cempty = ''.freeze
# @private
Cdelimiter = "\r\n".freeze
end
##
# commands
# Get the value associated with one or multiple keys
#
# cache.get(:a){ |v| p v }
# cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] }
#
def get *keys
raise ArgumentError unless block_given?
callback{
keys = keys.map{|k| k.to_s.gsub(/\s/,'_') }
send_data "get #{keys.join(' ')}\r\n"
@get_cbs << [keys, proc{ |values|
yield *keys.map{ |k| values[k] }
}]
}
end
# Set the value for a given key
#
# cache.set :a, 'hello'
# cache.set(:missing, 'abc'){ puts "stored the value!" }
#
def set key, val, exptime = 0, &cb
callback{
val = val.to_s
send_cmd :set, key, 0, exptime, val.respond_to?(:bytesize) ? val.bytesize : val.size, !block_given?
send_data val
send_data Cdelimiter
@set_cbs << cb if cb
}
end
# Gets multiple values as a hash
#
# cache.get_hash(:a, :b, :c, :d){ |h| puts h[:a] }
#
def get_hash *keys
raise ArgumentError unless block_given?
get *keys do |*values|
yield keys.inject({}){ |hash, k| hash.update k => values[keys.index(k)] }
end
end
# Delete the value associated with a key
#
# cache.del :a
# cache.del(:b){ puts "deleted the value!" }
#
def delete key, expires = 0, &cb
callback{
send_data "delete #{key} #{expires}#{cb ? '' : ' noreply'}\r\n"
@del_cbs << cb if cb
}
end
alias del delete
# Connect to a memcached server (must support NOREPLY, memcached >= 1.2.4)
def self.connect host = 'localhost', port = 11211
EM.connect host, port, self, host, port
end
def send_cmd cmd, key, flags = 0, exptime = 0, bytes = 0, noreply = false
send_data "#{cmd} #{key} #{flags} #{exptime} #{bytes}#{noreply ? ' noreply' : ''}\r\n"
end
private :send_cmd
##
# errors
# @private
class ParserError < StandardError
end
##
# em hooks
# @private
def initialize host, port = 11211
@host, @port = host, port
end
# @private
def connection_completed
@get_cbs = []
@set_cbs = []
@del_cbs = []
@values = {}
@reconnecting = false
@connected = true
succeed
# set_delimiter "\r\n"
# set_line_mode
end
#--
# 19Feb09 Switched to a custom parser, LineText2 is recursive and can cause
# stack overflows when there is too much data.
# include EM::P::LineText2
# @private
def receive_data data
(@buffer||='') << data
while index = @buffer.index(Cdelimiter)
begin
line = @buffer.slice!(0,index+2)
process_cmd line
rescue ParserError
@buffer[0...0] = line
break
end
end
end
#--
# def receive_line line
# @private
def process_cmd line
case line.strip
when /^VALUE\s+(.+?)\s+(\d+)\s+(\d+)/ # VALUE <key> <flags> <bytes>
bytes = Integer($3)
# set_binary_mode bytes+2
# @cur_key = $1
if @buffer.size >= bytes + 2
@values[$1] = @buffer.slice!(0,bytes)
@buffer.slice!(0,2) # \r\n
else
raise ParserError
end
when Cend # END
if entry = @get_cbs.shift
keys, cb = entry
cb.call(@values)
end
@values = {}
when Cstored # STORED
if cb = @set_cbs.shift
cb.call(true)
end
when Cdeleted # DELETED
if cb = @del_cbs.shift
cb.call(true)
end
when Cunknown # NOT_FOUND
if cb = @del_cbs.shift
cb.call(false)
end
else
p [:MEMCACHE_UNKNOWN, line]
end
end
#--
# def receive_binary_data data
# @values[@cur_key] = data[0..-3]
# end
# @private
def unbind
if @connected or @reconnecting
EM.add_timer(1){ reconnect @host, @port }
@connected = false
@reconnecting = true
@deferred_status = nil
else
raise 'Unable to connect to memcached server'
end
end
end
end
end
if __FILE__ == $0
# ruby -I ext:lib -r eventmachine -rubygems lib/protocols/memcache.rb
require 'em/spec'
# @private
class TestConnection
include EM::P::Memcache
def send_data data
sent_data << data
end
def sent_data
@sent_data ||= ''
end
def initialize
connection_completed
end
end
EM.describe EM::Protocols::Memcache do
before{
@c = TestConnection.new
}
should 'send get requests' do
@c.get('a'){}
@c.sent_data.should == "get a\r\n"
done
end
should 'send set requests' do
@c.set('a', 1){}
@c.sent_data.should == "set a 0 0 1\r\n1\r\n"
done
end
should 'use noreply on set without block' do
@c.set('a', 1)
@c.sent_data.should == "set a 0 0 1 noreply\r\n1\r\n"
done
end
should 'send delete requests' do
@c.del('a')
@c.sent_data.should == "delete a 0 noreply\r\n"
done
end
should 'work when get returns no values' do
@c.get('a'){ |a|
a.should.be.nil
done
}
@c.receive_data "END\r\n"
end
should 'invoke block on set' do
@c.set('a', 1){
done
}
@c.receive_data "STORED\r\n"
end
should 'invoke block on delete' do
@c.delete('a'){ |found|
found.should.be.false
}
@c.delete('b'){ |found|
found.should.be.true
done
}
@c.receive_data "NOT_FOUND\r\n"
@c.receive_data "DELETED\r\n"
end
should 'parse split responses' do
@c.get('a'){ |a|
a.should == 'abc'
done
}
@c.receive_data "VAL"
@c.receive_data "UE a 0 "
@c.receive_data "3\r\n"
@c.receive_data "ab"
@c.receive_data "c"
@c.receive_data "\r\n"
@c.receive_data "EN"
@c.receive_data "D\r\n"
end
end
end
@@ -0,0 +1,46 @@
module EventMachine
module Protocols
# ObjectProtocol allows for easy communication using marshaled ruby objects
#
# module RubyServer
# include EM::P::ObjectProtocol
#
# def receive_object obj
# send_object({'you said' => obj})
# end
# end
#
module ObjectProtocol
# By default returns Marshal, override to return JSON or YAML, or any
# other serializer/deserializer responding to #dump and #load.
def serializer
Marshal
end
# @private
def receive_data data
(@buf ||= '') << data
while @buf.size >= 4
if @buf.size >= 4+(size=@buf.unpack('N').first)
@buf.slice!(0,4)
receive_object serializer.load(@buf.slice!(0,size))
else
break
end
end
end
# Invoked with ruby objects received over the network
def receive_object obj
# stub
end
# Sends a ruby object over the network
def send_object obj
data = serializer.dump(obj)
send_data [data.respond_to?(:bytesize) ? data.bytesize : data.size, data].pack('Na*')
end
end
end
end
@@ -0,0 +1,246 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 November 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
require 'postgres-pr/message'
require 'postgres-pr/connection'
require 'stringio'
# @private
class StringIO
# Reads exactly +n+ bytes.
#
# If the data read is nil an EOFError is raised.
#
# If the data read is too short an IOError is raised
def readbytes(n)
str = read(n)
if str == nil
raise EOFError, "End of file reached"
end
if str.size < n
raise IOError, "data truncated"
end
str
end
alias read_exactly_n_bytes readbytes
end
module EventMachine
module Protocols
# PROVISIONAL IMPLEMENTATION of an evented Postgres client.
# This implements version 3 of the Postgres wire protocol, which will work
# with any Postgres version from roughly 7.4 onward.
#
# Objective: we want to access Postgres databases without requiring threads.
# Until now this has been a problem because the Postgres client implementations
# have all made use of blocking I/O calls, which is incompatible with a
# thread-free evented model.
#
# But rather than re-implement the Postgres Wire3 protocol, we're taking advantage
# of the existing postgres-pr library, which was originally written by Michael
# Neumann but (at this writing) appears to be no longer maintained. Still, it's
# in basically a production-ready state, and the wire protocol isn't that complicated
# anyway.
#
# We're tucking in a bunch of require statements that may not be present in garden-variety
# EM installations. Until we find a good way to only require these if a program
# requires postgres, this file will need to be required explicitly.
#
# We need to monkeypatch StringIO because it lacks the #readbytes method needed
# by postgres-pr.
# The StringIO monkeypatch is lifted from the standard library readbytes.rb,
# which adds method #readbytes directly to class IO. But StringIO is not a subclass of IO.
# It is modified to raise an IOError instead of TruncatedDataException since the exception is unused.
#
# We cloned the handling of postgres messages from lib/postgres-pr/connection.rb
# in the postgres-pr library, and modified it for event-handling.
#
# TODO: The password handling in dispatch_conn_message is totally incomplete.
#
#
# We return Deferrables from the user-level operations surfaced by this interface.
# Experimentally, we're using the pattern of always returning a boolean value as the
# first argument of a deferrable callback to indicate success or failure. This is
# instead of the traditional pattern of calling Deferrable#succeed or #fail, and
# requiring the user to define both a callback and an errback function.
#
# === Usage
# EM.run {
# db = EM.connect_unix_domain( "/tmp/.s.PGSQL.5432", EM::P::Postgres3 )
# db.connect( dbname, username, psw ).callback do |status|
# if status
# db.query( "select * from some_table" ).callback do |status, result, errors|
# if status
# result.rows.each do |row|
# p row
# end
# end
# end
# end
# end
# }
class Postgres3 < EventMachine::Connection
include PostgresPR
def initialize
@data = ""
@params = {}
end
def connect db, user, psw=nil
d = EM::DefaultDeferrable.new
d.timeout 15
if @pending_query || @pending_conn
d.succeed false, "Operation already in progress"
else
@pending_conn = d
prms = {"user"=>user, "database"=>db}
@user = user
if psw
@password = psw
#prms["password"] = psw
end
send_data PostgresPR::StartupMessage.new( 3 << 16, prms ).dump
end
d
end
def query sql
d = EM::DefaultDeferrable.new
d.timeout 15
if @pending_query || @pending_conn
d.succeed false, "Operation already in progress"
else
@r = PostgresPR::Connection::Result.new
@e = []
@pending_query = d
send_data PostgresPR::Query.dump(sql)
end
d
end
def receive_data data
@data << data
while @data.length >= 5
pktlen = @data[1...5].unpack("N").first
if @data.length >= (1 + pktlen)
pkt = @data.slice!(0...(1+pktlen))
m = StringIO.open( pkt, "r" ) {|io| PostgresPR::Message.read( io ) }
if @pending_conn
dispatch_conn_message m
elsif @pending_query
dispatch_query_message m
else
raise "Unexpected message from database"
end
else
break # very important, break out of the while
end
end
end
def unbind
if o = (@pending_query || @pending_conn)
o.succeed false, "lost connection"
end
end
# Cloned and modified from the postgres-pr.
def dispatch_conn_message msg
case msg
when AuthentificationClearTextPassword
raise ArgumentError, "no password specified" if @password.nil?
send_data PasswordMessage.new(@password).dump
when AuthentificationCryptPassword
raise ArgumentError, "no password specified" if @password.nil?
send_data PasswordMessage.new(@password.crypt(msg.salt)).dump
when AuthentificationMD5Password
raise ArgumentError, "no password specified" if @password.nil?
require 'digest/md5'
m = Digest::MD5.hexdigest(@password + @user)
m = Digest::MD5.hexdigest(m + msg.salt)
m = 'md5' + m
send_data PasswordMessage.new(m).dump
when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential
raise "unsupported authentification"
when AuthentificationOk
when ErrorResponse
raise msg.field_values.join("\t")
when NoticeResponse
@notice_processor.call(msg) if @notice_processor
when ParameterStatus
@params[msg.key] = msg.value
when BackendKeyData
# TODO
#p msg
when ReadyForQuery
# TODO: use transaction status
pc,@pending_conn = @pending_conn,nil
pc.succeed true
else
raise "unhandled message type"
end
end
# Cloned and modified from the postgres-pr.
def dispatch_query_message msg
case msg
when DataRow
@r.rows << msg.columns
when CommandComplete
@r.cmd_tag = msg.cmd_tag
when ReadyForQuery
pq,@pending_query = @pending_query,nil
pq.succeed true, @r, @e
when RowDescription
@r.fields = msg.fields
when CopyInResponse
when CopyOutResponse
when EmptyQueryResponse
when ErrorResponse
# TODO
@e << msg
when NoticeResponse
@notice_processor.call(msg) if @notice_processor
else
# TODO
end
end
end
end
end
@@ -0,0 +1,175 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 November 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
module EventMachine
module Protocols
# Implements SASL authd.
# This is a very, very simple protocol that mimics the one used
# by saslauthd and pwcheck, two outboard daemons included in the
# standard SASL library distro.
# The only thing this is really suitable for is SASL PLAIN
# (user+password) authentication, but the SASL libs that are
# linked into standard servers (like imapd and sendmail) implement
# the other ones.
#
# SASL-auth is intended for reasonably fast operation inside a
# single machine, so it has no transport-security (although there
# have been multi-machine extensions incorporating transport-layer
# encryption).
#
# The standard saslauthd module generally runs privileged and does
# its work by referring to the system-account files.
#
# This feature was added to EventMachine to enable the development
# of custom authentication/authorization engines for standard servers.
#
# To use SASLauth, include it in a class that subclasses EM::Connection,
# and reimplement the validate method.
#
# The typical way to incorporate this module into an authentication
# daemon would be to set it as the handler for a UNIX-domain socket.
# The code might look like this:
#
# EM.start_unix_domain_server( "/var/run/saslauthd/mux", MyHandler )
# File.chmod( 0777, "/var/run/saslauthd/mux")
#
# The chmod is probably needed to ensure that unprivileged clients can
# access the UNIX-domain socket.
#
# It's also a very good idea to drop superuser privileges (if any), after
# the UNIX-domain socket has been opened.
#--
# Implementation details: assume the client can send us pipelined requests,
# and that the client will close the connection.
#
# The client sends us four values, each encoded as a two-byte length field in
# network order followed by the specified number of octets.
# The fields specify the username, password, service name (such as imap),
# and the "realm" name. We send back the barest minimum reply, a single
# field also encoded as a two-octet length in network order, followed by
# either "NO" or "OK" - simplicity itself.
#
# We enforce a maximum field size just as a sanity check.
# We do NOT automatically time out the connection.
#
# The code we use to parse out the values is ugly and probably slow.
# Improvements welcome.
#
module SASLauth
MaxFieldSize = 128*1024
def post_init
super
@sasl_data = ""
@sasl_values = []
end
def receive_data data
@sasl_data << data
while @sasl_data.length >= 2
len = (@sasl_data[0,2].unpack("n")).first
raise "SASL Max Field Length exceeded" if len > MaxFieldSize
if @sasl_data.length >= (len + 2)
@sasl_values << @sasl_data[2,len]
@sasl_data.slice!(0...(2+len))
if @sasl_values.length == 4
send_data( validate(*@sasl_values) ? "\0\002OK" : "\0\002NO" )
@sasl_values.clear
end
else
break
end
end
end
def validate username, psw, sysname, realm
p username
p psw
p sysname
p realm
true
end
end
# Implements the SASL authd client protocol.
# This is a very, very simple protocol that mimics the one used
# by saslauthd and pwcheck, two outboard daemons included in the
# standard SASL library distro.
# The only thing this is really suitable for is SASL PLAIN
# (user+password) authentication, but the SASL libs that are
# linked into standard servers (like imapd and sendmail) implement
# the other ones.
#
# You can use this module directly as a handler for EM Connections,
# or include it in a module or handler class of your own.
#
# First connect to a SASL server (it's probably a TCP server, or more
# likely a Unix-domain socket). Then call the #validate? method,
# passing at least a username and a password. #validate? returns
# a Deferrable which will either succeed or fail, depending
# on the status of the authentication operation.
#
module SASLauthclient
MaxFieldSize = 128*1024
def validate? username, psw, sysname=nil, realm=nil
str = [username, psw, sysname, realm].map {|m|
[(m || "").length, (m || "")]
}.flatten.pack( "nA*" * 4 )
send_data str
d = EM::DefaultDeferrable.new
@queries.unshift d
d
end
def post_init
@sasl_data = ""
@queries = []
end
def receive_data data
@sasl_data << data
while @sasl_data.length > 2
len = (@sasl_data[0,2].unpack("n")).first
raise "SASL Max Field Length exceeded" if len > MaxFieldSize
if @sasl_data.length >= (len + 2)
val = @sasl_data[2,len]
@sasl_data.slice!(0...(2+len))
q = @queries.pop
(val == "NO") ? q.fail : q.succeed
else
break
end
end
end
end
end
end
@@ -0,0 +1,394 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 July 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
require 'ostruct'
module EventMachine
module Protocols
# Simple SMTP client
#
# @example
# email = EM::Protocols::SmtpClient.send(
# :domain=>"example.com",
# :host=>'localhost',
# :port=>25, # optional, defaults 25
# :starttls=>true, # use ssl
# :from=>"sender@example.com",
# :to=> ["to_1@example.com", "to_2@example.com"],
# :header=> {"Subject" => "This is a subject line"},
# :body=> "This is the body of the email"
# )
# email.callback{
# puts 'Email sent!'
# }
# email.errback{ |e|
# puts 'Email failed!'
# }
#
# Sending generated emails (using Mail)
#
# mail = Mail.new do
# from 'alice@example.com'
# to 'bob@example.com'
# subject 'This is a test email'
# body 'Hello, world!'
# end
#
# email = EM::P::SmtpClient.send(
# :domain=>'example.com',
# :from=>mail.from.first,
# :to=>mail.to,
# :message=>mail.to_s
# )
#
class SmtpClient < Connection
include EventMachine::Deferrable
include EventMachine::Protocols::LineText2
def initialize
@succeeded = nil
@responder = nil
@code = nil
@msg = nil
end
# :host => required String
# a string containing the IP address or host name of the SMTP server to connect to.
# :port => optional
# defaults to 25.
# :domain => required String
# This is passed as the argument to the EHLO command.
# :starttls => optional Boolean
# If it evaluates true, then the client will initiate STARTTLS with
# the server, and abort the connection if the negotiation doesn't succeed.
# TODO, need to be able to pass certificate parameters with this option.
# :auth => optional Hash of auth parameters
# If not given, then no auth will be attempted.
# (In that case, the connection will be aborted if the server requires auth.)
# Specify the hash value :type to determine the auth type, along with additional parameters
# depending on the type.
# Currently only :type => :plain is supported. Pass additional parameters :username (String),
# and :password (either a String or a Proc that will be called at auth-time).
#
# @example
# :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"}
#
# :from => required String
# Specifies the sender of the message. Will be passed as the argument
# to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters.
# The connection will abort if the server rejects the value.
# :to => required String or Array of Strings
# The recipient(s) of the message. Do NOT enclose
# any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more
# recipients are rejected by the server. (Of course, if ALL of them are, the server will most
# likely trigger an error when we try to send data.) An array of codes containing the status
# of each requested recipient is available after the call completes. TODO, we should define
# an overridable stub that will be called on rejection of a recipient or a sender, giving
# user code the chance to try again or abort the connection.
#
# One of either :message, :content, or :header and :body is required:
#
# :message => String
# A valid RFC2822 Internet Message.
# :content => String
# Raw data which MUST be in correct SMTP body format, with escaped leading dots and a trailing
# dot line.
# :header => String or Hash of values to be transmitted in the header of the message.
# The hash keys are the names of the headers (do NOT append a trailing colon), and the values
# are strings containing the header values. TODO, support Arrays of header values, which would
# cause us to send that specific header line more than once.
#
# @example
# :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"}
#
# :body => Optional String or Array of Strings, defaults blank.
# This will be passed as the body of the email message.
# TODO, this needs to be significantly beefed up. As currently written, this requires the caller
# to properly format the input into CRLF-delimited lines of 7-bit characters in the standard
# SMTP transmission format. We need to be able to automatically convert binary data, and add
# correct line-breaks to text data.
#
# :verbose => Optional.
# If true, will cause a lot of information (including the server-side of the
# conversation) to be dumped to $>.
#
def self.send args={}
args[:port] ||= 25
args[:body] ||= ""
=begin
(I don't think it's possible for EM#connect to throw an exception under normal
circumstances, so this original code is stubbed out. A connect-failure will result
in the #unbind method being called without calling #connection_completed.)
begin
EventMachine.connect( args[:host], args[:port], self) {|c|
# According to the EM docs, we will get here AFTER post_init is called.
c.args = args
c.set_comm_inactivity_timeout 60
}
rescue
# We'll get here on a connect error. This code mimics the effect
# of a call to invoke_internal_error. Would be great to DRY this up.
# (Actually, it may be that we never get here, if EM#connect catches
# its errors internally.)
d = EM::DefaultDeferrable.new
d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]})
d
end
=end
EventMachine.connect( args[:host], args[:port], self) {|c|
# According to the EM docs, we will get here AFTER post_init is called.
c.args = args
c.set_comm_inactivity_timeout 60
}
end
attr_writer :args
# @private
def post_init
@return_values = OpenStruct.new
@return_values.start_time = Time.now
end
# @private
def connection_completed
@responder = :receive_signon
@msg = []
end
# We can get here in a variety of ways, all of them being failures unless
# the @succeeded flag is set. If a protocol success was recorded, then don't
# set a deferred success because the caller will already have done it
# (no need to wait until the connection closes to invoke the callbacks).
#
# @private
def unbind
unless @succeeded
@return_values.elapsed_time = Time.now - @return_values.start_time
@return_values.responder = @responder
@return_values.code = @code
@return_values.message = @msg
set_deferred_status(:failed, @return_values)
end
end
# @private
def receive_line ln
$>.puts ln if @args[:verbose]
@range = ln[0...1].to_i
@code = ln[0...3].to_i
@msg << ln[4..-1]
unless ln[3...4] == '-'
$>.puts @responder if @args[:verbose]
send @responder
@msg.clear
end
end
private
# We encountered an error from the server and will close the connection.
# Use the error and message the server returned.
#
def invoke_error
@return_values.elapsed_time = Time.now - @return_values.start_time
@return_values.responder = @responder
@return_values.code = @code
@return_values.message = @msg
set_deferred_status :failed, @return_values
send_data "QUIT\r\n"
close_connection_after_writing
end
# We encountered an error on our side of the protocol and will close the connection.
# Use an extra-protocol error code (900) and use the message from the caller.
#
def invoke_internal_error msg = "???"
@return_values.elapsed_time = Time.now - @return_values.start_time
@return_values.responder = @responder
@return_values.code = 900
@return_values.message = msg
set_deferred_status :failed, @return_values
send_data "QUIT\r\n"
close_connection_after_writing
end
def send_ehlo
send_data "EHLO #{@args[:domain]}\r\n"
end
def receive_signon
return invoke_error unless @range == 2
send_ehlo
@responder = :receive_ehlo_response
end
def receive_ehlo_response
return invoke_error unless @range == 2
@server_caps = @msg
invoke_starttls
end
def invoke_starttls
if @args[:starttls]
# It would be more sociable to first ask if @server_caps contains
# the string "STARTTLS" before we invoke it, but hey, life's too short.
send_data "STARTTLS\r\n"
@responder = :receive_starttls_response
else
invoke_auth
end
end
def receive_starttls_response
return invoke_error unless @range == 2
start_tls
invoke_ehlo_over_tls
end
def invoke_ehlo_over_tls
send_ehlo
@responder = :receive_ehlo_over_tls_response
end
def receive_ehlo_over_tls_response
return invoke_error unless @range == 2
invoke_auth
end
# Perform an authentication. If the caller didn't request one, then fall through
# to the mail-from state.
def invoke_auth
if @args[:auth]
if @args[:auth][:type] == :plain
psw = @args[:auth][:password]
if psw.respond_to?(:call)
psw = psw.call
end
#str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp
str = ["\0#{@args[:auth][:username]}\0#{psw}"].pack("m").gsub(/\n/, '')
send_data "AUTH PLAIN #{str}\r\n"
@responder = :receive_auth_response
else
return invoke_internal_error("unsupported auth type")
end
else
invoke_mail_from
end
end
def receive_auth_response
return invoke_error unless @range == 2
invoke_mail_from
end
def invoke_mail_from
send_data "MAIL FROM: <#{@args[:from]}>\r\n"
@responder = :receive_mail_from_response
end
def receive_mail_from_response
return invoke_error unless @range == 2
invoke_rcpt_to
end
def invoke_rcpt_to
@rcpt_responses ||= []
l = @rcpt_responses.length
to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s]
if l < to.length
send_data "RCPT TO: <#{to[l]}>\r\n"
@responder = :receive_rcpt_to_response
else
e = @rcpt_responses.select {|rr| rr.last == 2}
if e and e.length > 0
invoke_data
else
invoke_error
end
end
end
def receive_rcpt_to_response
@rcpt_responses << [@code, @msg, @range]
invoke_rcpt_to
end
def escape_leading_dots(s)
s.gsub(/^\./, '..')
end
def invoke_data
send_data "DATA\r\n"
@responder = :receive_data_response
end
def receive_data_response
return invoke_error unless @range == 3
# The data to send can be given in either @args[:message], @args[:content], or the
# combination of @args[:header] and @args[:body].
#
# - @args[:message] (String) MUST be a valid RFC2822 Internet Message
#
# - @args[:content] (String) MUST be in correct SMTP body format, with escaped
# leading dots and a trailing dot line
#
# - @args[:header] (Hash or String)
# - @args[:body] (Array or String)
if @args[:message]
send_data escape_leading_dots(@args[:message].to_s)
send_data "\r\n.\r\n"
elsif @args[:content]
send_data @args[:content].to_s
else
# The header can be a hash or an array.
if @args[:header].is_a?(Hash)
(@args[:header] || {}).each {|k,v| send_data escape_leading_dots("#{k}: #{v}\r\n") }
else
send_data escape_leading_dots(@args[:header].to_s)
end
send_data "\r\n"
if @args[:body].is_a?(Array)
@args[:body].each {|e| send_data escape_leading_dots(e)}
else
send_data escape_leading_dots(@args[:body].to_s)
end
send_data "\r\n.\r\n"
end
@responder = :receive_message_response
end
def receive_message_response
return invoke_error unless @range == 2
send_data "QUIT\r\n"
close_connection_after_writing
@succeeded = true
@return_values.elapsed_time = Time.now - @return_values.start_time
@return_values.responder = @responder
@return_values.code = @code
@return_values.message = @msg
set_deferred_status :succeeded, @return_values
end
end
end
end
@@ -0,0 +1,666 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 July 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
module Protocols
# This is a protocol handler for the server side of SMTP.
# It's NOT a complete SMTP server obeying all the semantics of servers conforming to
# RFC2821. Rather, it uses overridable method stubs to communicate protocol states
# and data to user code. User code is responsible for doing the right things with the
# data in order to get complete and correct SMTP server behavior.
#
# Simple SMTP server example:
#
# class EmailServer < EM::P::SmtpServer
# def receive_plain_auth(user, pass)
# true
# end
#
# def get_server_domain
# "mock.smtp.server.local"
# end
#
# def get_server_greeting
# "mock smtp server greets you with impunity"
# end
#
# def receive_sender(sender)
# current.sender = sender
# true
# end
#
# def receive_recipient(recipient)
# current.recipient = recipient
# true
# end
#
# def receive_message
# current.received = true
# current.completed_at = Time.now
#
# p [:received_email, current]
# @current = OpenStruct.new
# true
# end
#
# def receive_ehlo_domain(domain)
# @ehlo_domain = domain
# true
# end
#
# def receive_data_command
# current.data = ""
# true
# end
#
# def receive_data_chunk(data)
# current.data << data.join("\n")
# true
# end
#
# def receive_transaction
# if @ehlo_domain
# current.ehlo_domain = @ehlo_domain
# @ehlo_domain = nil
# end
# true
# end
#
# def current
# @current ||= OpenStruct.new
# end
#
# def self.start(host = 'localhost', port = 1025)
# require 'ostruct'
# @server = EM.start_server host, port, self
# end
#
# def self.stop
# if @server
# EM.stop_server @server
# @server = nil
# end
# end
#
# def self.running?
# !!@server
# end
# end
#
# EM.run{ EmailServer.start }
#
#--
# Useful paragraphs in RFC-2821:
# 4.3.2: Concise list of command-reply sequences, in essence a text representation
# of the command state-machine.
#
# STARTTLS is defined in RFC2487.
# Observe that there are important rules governing whether a publicly-referenced server
# (meaning one whose Internet address appears in public MX records) may require the
# non-optional use of TLS.
# Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS.
class SmtpServer < EventMachine::Connection
include Protocols::LineText2
HeloRegex = /\AHELO\s*/i
EhloRegex = /\AEHLO\s*/i
QuitRegex = /\AQUIT/i
MailFromRegex = /\AMAIL FROM:\s*/i
RcptToRegex = /\ARCPT TO:\s*/i
DataRegex = /\ADATA/i
NoopRegex = /\ANOOP/i
RsetRegex = /\ARSET/i
VrfyRegex = /\AVRFY\s+/i
ExpnRegex = /\AEXPN\s+/i
HelpRegex = /\AHELP/i
StarttlsRegex = /\ASTARTTLS/i
AuthRegex = /\AAUTH\s+/i
# Class variable containing default parameters that can be overridden
# in application code.
# Individual objects of this class will make an instance-local copy of
# the class variable, so that they can be reconfigured on a per-instance
# basis.
#
# Chunksize is the number of data lines we'll buffer before
# sending them to the application. TODO, make this user-configurable.
#
@@parms = {
:chunksize => 4000,
:verbose => false
}
def self.parms= parms={}
@@parms.merge!(parms)
end
def initialize *args
super
@parms = @@parms
init_protocol_state
end
def parms= parms={}
@parms.merge!(parms)
end
# In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM,
# #post_init will execute BEFORE the block passed to #start_server, for any
# given accepted connection. Since in this class we'll probably be getting
# a lot of initialization parameters, we want the guts of post_init to
# run AFTER the application has initialized the connection object. So we
# use a spawn to schedule the post_init to run later.
# It's a little weird, I admit. A reasonable alternative would be to set
# parameters as a class variable and to do that before accepting any connections.
#
# OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.
#
def post_init
#send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
#(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
(EM.spawn {|x| x.send_server_greeting}).notify(self)
end
def send_server_greeting
send_data "220 #{get_server_greeting}\r\n"
end
def receive_line ln
@@parms[:verbose] and $>.puts ">>> #{ln}"
return process_data_line(ln) if @state.include?(:data)
return process_auth_line(ln) if @state.include?(:auth_incomplete)
case ln
when EhloRegex
process_ehlo $'.dup
when HeloRegex
process_helo $'.dup
when MailFromRegex
process_mail_from $'.dup
when RcptToRegex
process_rcpt_to $'.dup
when DataRegex
process_data
when RsetRegex
process_rset
when VrfyRegex
process_vrfy
when ExpnRegex
process_expn
when HelpRegex
process_help
when NoopRegex
process_noop
when QuitRegex
process_quit
when StarttlsRegex
process_starttls
when AuthRegex
process_auth $'.dup
else
process_unknown
end
end
# TODO - implement this properly, the implementation is a stub!
def process_help
send_data "250 Ok, but unimplemented\r\n"
end
# RFC2821, 3.5.3 Meaning of VRFY or EXPN Success Response:
# A server MUST NOT return a 250 code in response to a VRFY or EXPN
# command unless it has actually verified the address. In particular,
# a server MUST NOT return 250 if all it has done is to verify that the
# syntax given is valid. In that case, 502 (Command not implemented)
# or 500 (Syntax error, command unrecognized) SHOULD be returned.
#
# TODO - implement this properly, the implementation is a stub!
def process_vrfy
send_data "502 Command not implemented\r\n"
end
# TODO - implement this properly, the implementation is a stub!
def process_expn
send_data "502 Command not implemented\r\n"
end
#--
# This is called at several points to restore the protocol state
# to a pre-transaction state. In essence, we "forget" having seen
# any valid command except EHLO and STARTTLS.
# We also have to callback user code, in case they're keeping track
# of senders, recipients, and whatnot.
#
# We try to follow the convention of avoiding the verb "receive" for
# internal method names except receive_line (which we inherit), and
# using only receive_xxx for user-overridable stubs.
#
# init_protocol_state is called when we initialize the connection as
# well as during reset_protocol_state. It does NOT call the user
# override method. This enables us to promise the users that they
# won't see the overridable fire except after EHLO and RSET, and
# after a message has been received. Although the latter may be wrong.
# The standard may allow multiple DATA segments with the same set of
# senders and recipients.
#
def reset_protocol_state
init_protocol_state
s,@state = @state,[]
@state << :starttls if s.include?(:starttls)
@state << :ehlo if s.include?(:ehlo)
receive_transaction
end
def init_protocol_state
@state ||= []
end
#--
# EHLO/HELO is always legal, per the standard. On success
# it always clears buffers and initiates a mail "transaction."
# Which means that a MAIL FROM must follow.
#
# Per the standard, an EHLO/HELO or a RSET "initiates" an email
# transaction. Thereafter, MAIL FROM must be received before
# RCPT TO, before DATA. Not sure what this specific ordering
# achieves semantically, but it does make it easier to
# implement. We also support user-specified requirements for
# STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
# without fulfilling tls and/or auth, if the user specified either
# or both as required. We need to check the extension standard
# for auth to see if a credential is discarded after a RSET along
# with all the rest of the state. We'll behave as if it is.
# Now clearly, we can't discard tls after its been negotiated
# without dropping the connection, so that flag doesn't get cleared.
#
def process_ehlo domain
if receive_ehlo_domain domain
send_data "250-#{get_server_domain}\r\n"
if @@parms[:starttls]
send_data "250-STARTTLS\r\n"
end
if @@parms[:auth]
send_data "250-AUTH PLAIN\r\n"
end
send_data "250-NO-SOLICITING\r\n"
# TODO, size needs to be configurable.
send_data "250 SIZE 20000000\r\n"
reset_protocol_state
@state << :ehlo
else
send_data "550 Requested action not taken\r\n"
end
end
def process_helo domain
if receive_ehlo_domain domain.dup
send_data "250 #{get_server_domain}\r\n"
reset_protocol_state
@state << :ehlo
else
send_data "550 Requested action not taken\r\n"
end
end
def process_quit
send_data "221 Ok\r\n"
close_connection_after_writing
end
def process_noop
send_data "250 Ok\r\n"
end
def process_unknown
send_data "500 Unknown command\r\n"
end
#--
# So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
# TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
# response and a continuation of the auth conversation.
#
def process_auth str
if @state.include?(:auth)
send_data "503 auth already issued\r\n"
elsif str =~ /\APLAIN\s?/i
if $'.length == 0
# we got a partial response, so let the client know to send the rest
@state << :auth_incomplete
send_data("334 \r\n")
else
# we got the initial response, so go ahead & process it
process_auth_line($')
end
#elsif str =~ /\ALOGIN\s+/i
else
send_data "504 auth mechanism not available\r\n"
end
end
def process_auth_line(line)
plain = line.unpack("m").first
_,user,psw = plain.split("\000")
succeeded = proc {
send_data "235 authentication ok\r\n"
@state << :auth
}
failed = proc {
send_data "535 invalid authentication\r\n"
}
auth = receive_plain_auth user,psw
if auth.respond_to?(:callback)
auth.callback(&succeeded)
auth.errback(&failed)
else
(auth ? succeeded : failed).call
end
@state.delete :auth_incomplete
end
#--
# Unusually, we can deal with a Deferrable returned from the user application.
# This was added to deal with a special case in a particular application, but
# it would be a nice idea to add it to the other user-code callbacks.
#
def process_data
unless @state.include?(:rcpt)
send_data "503 Operation sequence error\r\n"
else
succeeded = proc {
send_data "354 Send it\r\n"
@state << :data
@databuffer = []
}
failed = proc {
send_data "550 Operation failed\r\n"
}
d = receive_data_command
if d.respond_to?(:callback)
d.callback(&succeeded)
d.errback(&failed)
else
(d ? succeeded : failed).call
end
end
end
def process_rset
reset_protocol_state
receive_reset
send_data "250 Ok\r\n"
end
def unbind
connection_ended
end
#--
# STARTTLS may not be issued before EHLO, or unless the user has chosen
# to support it.
#
# If :starttls_options is present and :starttls is set in the parms
# pass the options in :starttls_options to start_tls. Do this if you want to use
# your own certificate
# e.g. {:cert_chain_file => "/etc/ssl/cert.pem", :private_key_file => "/etc/ssl/private/cert.key"}
def process_starttls
if @@parms[:starttls]
if @state.include?(:starttls)
send_data "503 TLS Already negotiated\r\n"
elsif ! @state.include?(:ehlo)
send_data "503 EHLO required before STARTTLS\r\n"
else
send_data "220 Start TLS negotiation\r\n"
start_tls(@@parms[:starttls_options] || {})
@state << :starttls
end
else
process_unknown
end
end
#--
# Requiring TLS is touchy, cf RFC2784.
# Requiring AUTH seems to be much more reasonable.
# We don't currently support any notion of deriving an authentication from the TLS
# negotiation, although that would certainly be reasonable.
# We DON'T allow MAIL FROM to be given twice.
# We DON'T enforce all the various rules for validating the sender or
# the reverse-path (like whether it should be null), and notifying the reverse
# path in case of delivery problems. All of that is left to the calling application.
#
def process_mail_from sender
if (@@parms[:starttls]==:required and !@state.include?(:starttls))
send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
elsif (@@parms[:auth]==:required and !@state.include?(:auth))
send_data "550 This server requires authentication before MAIL FROM\r\n"
elsif @state.include?(:mail_from)
send_data "503 MAIL already given\r\n"
else
unless receive_sender sender
send_data "550 sender is unacceptable\r\n"
else
send_data "250 Ok\r\n"
@state << :mail_from
end
end
end
#--
# Since we require :mail_from to have been seen before we process RCPT TO,
# we don't need to repeat the tests for TLS and AUTH.
# Note that we don't remember or do anything else with the recipients.
# All of that is on the user code.
# TODO: we should enforce user-definable limits on the total number of
# recipients per transaction.
# We might want to make sure that a given recipient is only seen once, but
# for now we'll let that be the user's problem.
#
# User-written code can return a deferrable from receive_recipient.
#
def process_rcpt_to rcpt
unless @state.include?(:mail_from)
send_data "503 MAIL is required before RCPT\r\n"
else
succeeded = proc {
send_data "250 Ok\r\n"
@state << :rcpt unless @state.include?(:rcpt)
}
failed = proc {
send_data "550 recipient is unacceptable\r\n"
}
d = receive_recipient rcpt
if d.respond_to?(:set_deferred_status)
d.callback(&succeeded)
d.errback(&failed)
else
(d ? succeeded : failed).call
end
=begin
unless receive_recipient rcpt
send_data "550 recipient is unacceptable\r\n"
else
send_data "250 Ok\r\n"
@state << :rcpt unless @state.include?(:rcpt)
end
=end
end
end
# Send the incoming data to the application one chunk at a time, rather than
# one line at a time. That lets the application be a little more flexible about
# storing to disk, etc.
# Since we clear the chunk array every time we submit it, the caller needs to be
# aware to do things like dup it if he wants to keep it around across calls.
#
# Resets the transaction upon disposition of the incoming message.
# RFC5321 says this about the MAIL FROM command:
# "This command tells the SMTP-receiver that a new mail transaction is
# starting and to reset all its state tables and buffers, including any
# recipients or mail data."
#
# Equivalent behaviour is implemented by resetting after a completed transaction.
#
# User-written code can return a Deferrable as a response from receive_message.
#
def process_data_line ln
if ln == "."
if @databuffer.length > 0
receive_data_chunk @databuffer
@databuffer.clear
end
succeeded = proc {
send_data "250 Message accepted\r\n"
reset_protocol_state
}
failed = proc {
send_data "550 Message rejected\r\n"
reset_protocol_state
}
d = receive_message
if d.respond_to?(:set_deferred_status)
d.callback(&succeeded)
d.errback(&failed)
else
(d ? succeeded : failed).call
end
@state.delete :data
else
# slice off leading . if any
ln.slice!(0...1) if ln[0] == ?.
@databuffer << ln
if @databuffer.length > @@parms[:chunksize]
receive_data_chunk @databuffer
@databuffer.clear
end
end
end
#------------------------------------------
# Everything from here on can be overridden in user code.
# The greeting returned in the initial connection message to the client.
def get_server_greeting
"EventMachine SMTP Server"
end
# The domain name returned in the first line of the response to a
# successful EHLO or HELO command.
def get_server_domain
"Ok EventMachine SMTP Server"
end
# A false response from this user-overridable method will cause a
# 550 error to be returned to the remote client.
#
def receive_ehlo_domain domain
true
end
# Return true or false to indicate that the authentication is acceptable.
def receive_plain_auth user, password
true
end
# Receives the argument of the MAIL FROM command. Return false to
# indicate to the remote client that the sender is not accepted.
# This can only be successfully called once per transaction.
#
def receive_sender sender
true
end
# Receives the argument of a RCPT TO command. Can be given multiple
# times per transaction. Return false to reject the recipient.
#
def receive_recipient rcpt
true
end
# Sent when the remote peer issues the RSET command.
# Since RSET is not allowed to fail (according to the protocol),
# we ignore any return value from user overrides of this method.
#
def receive_reset
end
# Sent when the remote peer has ended the connection.
#
def connection_ended
end
# Called when the remote peer sends the DATA command.
# Returning false will cause us to send a 550 error to the peer.
# This can be useful for dealing with problems that arise from processing
# the whole set of sender and recipients.
#
def receive_data_command
true
end
# Sent when data from the remote peer is available. The size can be controlled
# by setting the :chunksize parameter. This call can be made multiple times.
# The goal is to strike a balance between sending the data to the application one
# line at a time, and holding all of a very large message in memory.
#
def receive_data_chunk data
@smtps_msg_size ||= 0
@smtps_msg_size += data.join.length
STDERR.write "<#{@smtps_msg_size}>"
end
# Sent after a message has been completely received. User code
# must return true or false to indicate whether the message has
# been accepted for delivery.
def receive_message
@@parms[:verbose] and $>.puts "Received complete message"
true
end
# This is called when the protocol state is reset. It happens
# when the remote client calls EHLO/HELO or RSET.
def receive_transaction
end
end
end
end
@@ -0,0 +1,66 @@
module EventMachine
module Protocols
# Basic SOCKS v4 client implementation
#
# Use as you would any regular connection:
#
# class MyConn < EM::P::Socks4
# def post_init
# send_data("sup")
# end
#
# def receive_data(data)
# send_data("you said: #{data}")
# end
# end
#
# EM.connect socks_host, socks_port, MyConn, host, port
#
class Socks4 < Connection
def initialize(host, port)
@host = Socket.gethostbyname(host).last
@port = port
@socks_error_code = nil
@buffer = ''
setup_methods
end
def setup_methods
class << self
def post_init; socks_post_init; end
def receive_data(*a); socks_receive_data(*a); end
end
end
def restore_methods
class << self
remove_method :post_init
remove_method :receive_data
end
end
def socks_post_init
header = [4, 1, @port, @host, 0].flatten.pack("CCnA4C")
send_data(header)
end
def socks_receive_data(data)
@buffer << data
return if @buffer.size < 8
header_resp = @buffer.slice! 0, 8
_, r = header_resp.unpack("cc")
if r != 90
@socks_error_code = r
close_connection
return
end
restore_methods
post_init
receive_data(@buffer) unless @buffer.empty?
end
end
end
end
@@ -0,0 +1,205 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 15 November 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
module EventMachine
module Protocols
# Implements Stomp (http://docs.codehaus.org/display/STOMP/Protocol).
#
# == Usage example
#
# module StompClient
# include EM::Protocols::Stomp
#
# def connection_completed
# connect :login => 'guest', :passcode => 'guest'
# end
#
# def receive_msg msg
# if msg.command == "CONNECTED"
# subscribe '/some/topic'
# else
# p ['got a message', msg]
# puts msg.body
# end
# end
# end
#
# EM.run{
# EM.connect 'localhost', 61613, StompClient
# }
#
module Stomp
include LineText2
class Message
# The command associated with the message, usually 'CONNECTED' or 'MESSAGE'
attr_accessor :command
# Hash containing headers such as destination and message-id
attr_accessor :header
alias :headers :header
# Body of the message
attr_accessor :body
# @private
def initialize
@header = {}
@state = :precommand
@content_length = nil
end
# @private
def consume_line line
if @state == :precommand
unless line =~ /\A\s*\Z/
@command = line
@state = :headers
end
elsif @state == :headers
if line == ""
if @content_length
yield( [:sized_text, @content_length+1] )
else
@state = :body
yield( [:unsized_text] )
end
elsif line =~ /\A([^:]+):(.+)\Z/
k = $1.dup.strip
v = $2.dup.strip
@header[k] = v
if k == "content-length"
@content_length = v.to_i
end
else
# This is a protocol error. How to signal it?
end
elsif @state == :body
@body = line
yield( [:dispatch] )
end
end
end
# @private
def send_frame verb, headers={}, body=""
body = body.to_s
ary = [verb, "\n"]
body_bytesize = body.bytesize if body.respond_to? :bytesize
body_bytesize ||= body.size
headers.each {|k,v| ary << "#{k}:#{v}\n" }
ary << "content-length: #{body_bytesize}\n"
ary << "content-type: text/plain; charset=UTF-8\n" unless headers.has_key? 'content-type'
ary << "\n"
ary << body
ary << "\0"
send_data ary.join
end
# @private
def receive_line line
@stomp_initialized || init_message_reader
@stomp_message.consume_line(line) {|outcome|
if outcome.first == :sized_text
set_text_mode outcome[1]
elsif outcome.first == :unsized_text
set_delimiter "\0"
elsif outcome.first == :dispatch
receive_msg(@stomp_message) if respond_to?(:receive_msg)
init_message_reader
end
}
end
# @private
def receive_binary_data data
@stomp_message.body = data[0..-2]
receive_msg(@stomp_message) if respond_to?(:receive_msg)
init_message_reader
end
# @private
def init_message_reader
@stomp_initialized = true
set_delimiter "\n"
set_line_mode
@stomp_message = Message.new
end
# Invoked with an incoming Stomp::Message received from the STOMP server
def receive_msg msg
# stub, overwrite this in your handler
end
# CONNECT command, for authentication
#
# connect :login => 'guest', :passcode => 'guest'
#
def connect parms={}
send_frame "CONNECT", parms
end
# SEND command, for publishing messages to a topic
#
# send '/topic/name', 'some message here'
#
def send destination, body, parms={}
send_frame "SEND", parms.merge( :destination=>destination ), body.to_s
end
# SUBSCRIBE command, for subscribing to topics
#
# subscribe '/topic/name', false
#
def subscribe dest, ack=false
send_frame "SUBSCRIBE", {:destination=>dest, :ack=>(ack ? "client" : "auto")}
end
# ACK command, for acknowledging receipt of messages
#
# module StompClient
# include EM::P::Stomp
#
# def connection_completed
# connect :login => 'guest', :passcode => 'guest'
# # subscribe with ack mode
# subscribe '/some/topic', true
# end
#
# def receive_msg msg
# if msg.command == "MESSAGE"
# ack msg.headers['message-id']
# puts msg.body
# end
# end
# end
#
def ack msgid
send_frame "ACK", 'message-id'=> msgid
end
end
end
end
@@ -0,0 +1,54 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 16 July 2006
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
module EventMachine
module Protocols
# @private
class TcpConnectTester < Connection
include EventMachine::Deferrable
def self.test( host, port )
EventMachine.connect( host, port, self )
end
def post_init
@start_time = Time.now
end
def connection_completed
@completed = true
set_deferred_status :succeeded, (Time.now - @start_time)
close_connection
end
def unbind
set_deferred_status :failed, (Time.now - @start_time) unless @completed
end
end
end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
module EventMachine
# A cross thread, reactor scheduled, linear queue.
#
# This class provides a simple queue abstraction on top of the reactor
# scheduler. It services two primary purposes:
#
# * API sugar for stateful protocols
# * Pushing processing onto the reactor thread
#
# @example
#
# q = EM::Queue.new
# q.push('one', 'two', 'three')
# 3.times do
# q.pop { |msg| puts(msg) }
# end
#
class Queue
def initialize
@sink = []
@drain = []
@popq = []
end
# Pop items off the queue, running the block on the reactor thread. The pop
# will not happen immediately, but at some point in the future, either in
# the next tick, if the queue has data, or when the queue is populated.
#
# @return [NilClass] nil
def pop(*a, &b)
cb = EM::Callback(*a, &b)
EM.schedule do
if @drain.empty?
@drain = @sink
@sink = []
end
if @drain.empty?
@popq << cb
else
cb.call @drain.shift
end
end
nil # Always returns nil
end
# Push items onto the queue in the reactor thread. The items will not appear
# in the queue immediately, but will be scheduled for addition during the
# next reactor tick.
def push(*items)
EM.schedule do
@sink.push(*items)
unless @popq.empty?
@drain = @sink
@sink = []
@popq.shift.call @drain.shift until @drain.empty? || @popq.empty?
end
end
end
alias :<< :push
# @return [Boolean]
# @note This is a peek, it's not thread safe, and may only tend toward accuracy.
def empty?
@drain.empty? && @sink.empty?
end
# @return [Integer] Queue size
# @note This is a peek, it's not thread safe, and may only tend toward accuracy.
def size
@drain.size + @sink.size
end
# @return [Integer] Waiting size
# @note This is a peek at the number of jobs that are currently waiting on the Queue
def num_waiting
@popq.size
end
end # Queue
end # EventMachine
@@ -0,0 +1,232 @@
module EventMachine
module DNS
class Resolver
def self.windows?
if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
require 'win32/resolv'
true
else
false
end
end
HOSTS_FILE = windows? ? Win32::Resolv.get_hosts_path : '/etc/hosts'
@hosts = nil
@nameservers = nil
@socket = nil
def self.resolve(hostname)
Request.new(socket, hostname)
end
def self.socket
if @socket && @socket.error?
@socket = Socket.open
else
@socket ||= Socket.open
end
end
def self.nameservers=(ns)
@nameservers = ns
end
def self.nameservers
return @nameservers if @nameservers
if windows?
_, ns = Win32::Resolv.get_resolv_info
return @nameservers = ns || []
end
@nameservers = []
IO.readlines('/etc/resolv.conf').each do |line|
if line =~ /^nameserver (.+)$/
@nameservers << $1.split(/\s+/).first
end
end
@nameservers
rescue
@nameservers = []
end
def self.nameserver
nameservers.shuffle.first
end
def self.hosts
return @hosts if @hosts
@hosts = {}
IO.readlines(HOSTS_FILE).each do |line|
next if line =~ /^#/
addr, host = line.split(/\s+/)
next unless addr && host
@hosts[host] ||= []
@hosts[host] << addr
end
@hosts
rescue
@hosts = {}
end
end
class RequestIdAlreadyUsed < RuntimeError; end
class Socket < EventMachine::Connection
def self.open
EventMachine::open_datagram_socket('0.0.0.0', 0, self)
end
def initialize
@nameserver = nil
end
def post_init
@requests = {}
end
def start_timer
@timer ||= EM.add_periodic_timer(0.1, &method(:tick))
end
def stop_timer
EM.cancel_timer(@timer)
@timer = nil
end
def unbind
end
def tick
@requests.each do |id,req|
req.tick
end
end
def register_request(id, req)
if @requests.has_key?(id)
raise RequestIdAlreadyUsed
else
@requests[id] = req
end
start_timer
end
def deregister_request(id, req)
@requests.delete(id)
stop_timer if @requests.length == 0
end
def send_packet(pkt)
send_datagram(pkt, nameserver, 53)
end
def nameserver=(ns)
@nameserver = ns
end
def nameserver
@nameserver || Resolver.nameserver
end
# Decodes the packet, looks for the request and passes the
# response over to the requester
def receive_data(data)
msg = nil
begin
msg = Resolv::DNS::Message.decode data
rescue
else
req = @requests[msg.id]
if req
@requests.delete(msg.id)
stop_timer if @requests.length == 0
req.receive_answer(msg)
end
end
end
end
class Request
include Deferrable
attr_accessor :retry_interval, :max_tries
def initialize(socket, hostname)
@socket = socket
@hostname = hostname
@tries = 0
@last_send = Time.at(0)
@retry_interval = 3
@max_tries = 5
if addrs = Resolver.hosts[hostname]
succeed addrs
else
EM.next_tick { tick }
end
end
def tick
# Break early if nothing to do
return if @last_send + @retry_interval > Time.now
if @tries < @max_tries
send
else
@socket.deregister_request(@id, self)
fail 'retries exceeded'
end
end
def receive_answer(msg)
addrs = []
msg.each_answer do |name,ttl,data|
if data.kind_of?(Resolv::DNS::Resource::IN::A) ||
data.kind_of?(Resolv::DNS::Resource::IN::AAAA)
addrs << data.address.to_s
end
end
if addrs.empty?
fail "rcode=#{msg.rcode}"
else
succeed addrs
end
end
private
def send
@tries += 1
@last_send = Time.now
@socket.send_packet(packet.encode)
end
def id
begin
@id = rand(65535)
@socket.register_request(@id, self)
rescue RequestIdAlreadyUsed
retry
end unless defined?(@id)
@id
end
def packet
msg = Resolv::DNS::Message.new
msg.id = id
msg.rd = 1
msg.add_question @hostname, Resolv::DNS::Resource::IN::A
msg
end
end
end
end
@@ -0,0 +1,84 @@
#--
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 25 Aug 2007
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
module EventMachine
# Support for Erlang-style processes.
#
class SpawnedProcess
# Send a message to the spawned process
def notify *x
me = self
EM.next_tick {
# A notification executes in the context of this
# SpawnedProcess object. That makes self and notify
# work as one would expect.
#
y = me.call(*x)
if y and y.respond_to?(:pull_out_yield_block)
a,b = y.pull_out_yield_block
set_receiver a
self.notify if b
end
}
end
alias_method :resume, :notify
alias_method :run, :notify # for formulations like (EM.spawn {xxx}).run
def set_receiver blk
(class << self ; self ; end).class_eval do
remove_method :call if method_defined? :call
define_method :call, blk
end
end
end
# @private
class YieldBlockFromSpawnedProcess
def initialize block, notify
@block = [block,notify]
end
def pull_out_yield_block
@block
end
end
# Spawn an erlang-style process
def self.spawn &block
s = SpawnedProcess.new
s.set_receiver block
s
end
# @private
def self.yield &block
return YieldBlockFromSpawnedProcess.new( block, false )
end
# @private
def self.yield_and_notify &block
return YieldBlockFromSpawnedProcess.new( block, true )
end
end
@@ -0,0 +1,118 @@
module EventMachine
# Streams a file over a given connection. Streaming begins once the object is
# instantiated. Typically FileStreamer instances are not reused.
#
# Streaming uses buffering for files larger than 16K and uses so-called fast file reader (a C++ extension)
# if available (it is part of eventmachine gem itself).
#
# @example
#
# module FileSender
# def post_init
# streamer = EventMachine::FileStreamer.new(self, '/tmp/bigfile.tar')
# streamer.callback{
# # file was sent successfully
# close_connection_after_writing
# }
# end
# end
#
#
# @author Francis Cianfrocca
class FileStreamer
include Deferrable
# Use mapped streamer for files bigger than 16k
MappingThreshold = 16384
# Wait until next tick to send more data when 50k is still in the outgoing buffer
BackpressureLevel = 50000
# Send 16k chunks at a time
ChunkSize = 16384
# @param [EventMachine::Connection] connection
# @param [String] filename File path
#
# @option args [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics.
def initialize connection, filename, args = {}
@connection = connection
@http_chunks = args[:http_chunks]
if File.exist?(filename)
@size = File.size(filename)
if @size <= MappingThreshold
stream_without_mapping filename
else
stream_with_mapping filename
end
else
fail "file not found"
end
end
# @private
def stream_without_mapping filename
if @http_chunks
@connection.send_data "#{@size.to_s(16)}\r\n"
@connection.send_file_data filename
@connection.send_data "\r\n0\r\n\r\n"
else
@connection.send_file_data filename
end
succeed
end
private :stream_without_mapping
# @private
def stream_with_mapping filename
ensure_mapping_extension_is_present
@position = 0
@mapping = EventMachine::FastFileReader::Mapper.new filename
stream_one_chunk
end
private :stream_with_mapping
# Used internally to stream one chunk at a time over multiple reactor ticks
# @private
def stream_one_chunk
loop {
if @position < @size
if @connection.get_outbound_data_size > BackpressureLevel
EventMachine::next_tick {stream_one_chunk}
break
else
len = @size - @position
len = ChunkSize if (len > ChunkSize)
@connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks
@connection.send_data( @mapping.get_chunk( @position, len ))
@connection.send_data("\r\n") if @http_chunks
@position += len
end
else
@connection.send_data "0\r\n\r\n" if @http_chunks
@mapping.close
succeed
break
end
}
end
#
# We use an outboard extension class to get memory-mapped files.
# It's outboard to avoid polluting the core distro, but that means
# there's a "hidden" dependency on it. The first time we get here in
# any run, try to load up the dependency extension. User code will see
# a LoadError if it's not available, but code that doesn't require
# mapped files will work fine without it. This is a somewhat difficult
# compromise between usability and proper modularization.
#
# @private
def ensure_mapping_extension_is_present
@@fastfilereader ||= (require 'fastfilereaderext')
end
private :ensure_mapping_extension_is_present
end # FileStreamer
end # EventMachine
@@ -0,0 +1,90 @@
module EventMachine
# = EventMachine::ThreadedResource
#
# A threaded resource is a "quick and dirty" wrapper around the concept of
# wiring up synchronous code into a standard EM::Pool. This is useful to keep
# interfaces coherent and provide a simple approach at "making an interface
# async-ish".
#
# General usage is to wrap libraries that do not support EventMachine, or to
# have a specific number of dedicated high-cpu worker resources.
#
# == Basic Usage example
#
# This example requires the cassandra gem. The cassandra gem contains an
# EventMachine interface, but it's sadly Fiber based and thus only works on
# 1.9. It also requires (potentially) complex stack switching logic to reach
# completion of nested operations. By contrast this approach provides a block
# in which normal synchronous code can occur, but makes no attempt to wire the
# IO into EventMachines C++ IO implementations, instead relying on the reactor
# pattern in rb_thread_select.
#
# cassandra_dispatcher = ThreadedResource.new do
# Cassandra.new('allthethings', '127.0.0.1:9160')
# end
#
# pool = EM::Pool.new
#
# pool.add cassandra_dispatcher
#
# # If we don't care about the result:
# pool.perform do |dispatcher|
# # The following block executes inside a dedicated thread, and should not
# # access EventMachine things:
# dispatcher.dispatch do |cassandra|
# cassandra.insert(:Things, '10', 'stuff' => 'things')
# end
# end
#
# # Example where we care about the result:
# pool.perform do |dispatcher|
# # The dispatch block is executed in the resources thread.
# completion = dispatcher.dispatch do |cassandra|
# cassandra.get(:Things, '10', 'stuff')
# end
#
# # This block will be yielded on the EM thread:
# completion.callback do |result|
# EM.do_something_with(result)
# end
#
# completion
# end
class ThreadedResource
# The block should return the resource that will be yielded in a dispatch.
def initialize
@resource = yield
@running = true
@queue = ::Queue.new
@thread = Thread.new do
@queue.pop.call while @running
end
end
# Called on the EM thread, generally in a perform block to return a
# completion for the work.
def dispatch
completion = EM::Completion.new
@queue << lambda do
begin
result = yield @resource
completion.succeed result
rescue => e
completion.fail e
end
end
completion
end
# Kill the internal thread. should only be used to cleanup - generally
# only required for tests.
def shutdown
@running = false
@queue << lambda {}
@thread.join
end
end
end
@@ -0,0 +1,85 @@
module EventMachine
# Creates and immediately starts an EventMachine::TickLoop
def self.tick_loop(*a, &b)
TickLoop.new(*a, &b).start
end
# A TickLoop is useful when one needs to distribute amounts of work
# throughout ticks in order to maintain response times. It is also useful for
# simple repeated checks and metrics.
# @example
# # Here we run through an array one item per tick until it is empty,
# # printing each element.
# # When the array is empty, we return :stop from the callback, and the
# # loop will terminate.
# # When the loop terminates, the on_stop callbacks will be called.
# EM.run do
# array = (1..100).to_a
#
# tickloop = EM.tick_loop do
# if array.empty?
# :stop
# else
# puts array.shift
# end
# end
#
# tickloop.on_stop { EM.stop }
# end
#
class TickLoop
# Arguments: A callback (EM::Callback) to call each tick. If the call
# returns +:stop+ then the loop will be stopped. Any other value is
# ignored.
def initialize(*a, &b)
@work = EM::Callback(*a, &b)
@stops = []
@stopped = true
end
# Arguments: A callback (EM::Callback) to call once on the next stop (or
# immediately if already stopped).
def on_stop(*a, &b)
if @stopped
EM::Callback(*a, &b).call
else
@stops << EM::Callback(*a, &b)
end
end
# Stop the tick loop immediately, and call it's on_stop callbacks.
def stop
@stopped = true
until @stops.empty?
@stops.shift.call
end
end
# Query if the loop is stopped.
def stopped?
@stopped
end
# Start the tick loop, will raise argument error if the loop is already
# running.
def start
raise ArgumentError, "double start" unless @stopped
@stopped = false
schedule
end
private
def schedule
EM.next_tick do
next if @stopped
if @work.call == :stop
stop
else
schedule
end
end
self
end
end
end
@@ -0,0 +1,61 @@
module EventMachine
# Creates a one-time timer
#
# timer = EventMachine::Timer.new(5) do
# # this will never fire because we cancel it
# end
# timer.cancel
#
class Timer
# Create a new timer that fires after a given number of seconds
def initialize interval, callback=nil, &block
@signature = EventMachine::add_timer(interval, callback || block)
end
# Cancel the timer
def cancel
EventMachine.send :cancel_timer, @signature
end
end
# Creates a periodic timer
#
# @example
# n = 0
# timer = EventMachine::PeriodicTimer.new(5) do
# puts "the time is #{Time.now}"
# timer.cancel if (n+=1) > 5
# end
#
class PeriodicTimer
# Create a new periodic timer that executes every interval seconds
def initialize interval, callback=nil, &block
@interval = interval
@code = callback || block
@cancelled = false
@work = method(:fire)
schedule
end
# Cancel the periodic timer
def cancel
@cancelled = true
end
# Fire the timer every interval seconds
attr_accessor :interval
# @private
def schedule
EventMachine::add_timer @interval, @work
end
# @private
def fire
unless @cancelled
@code.call
schedule
end
end
end
end
@@ -0,0 +1,3 @@
module EventMachine
VERSION = "1.2.7"
end

Some files were not shown because too many files have changed in this diff Show More