#
# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2018 Matthieu Le Guellaut <leguellaut.matthieu@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
+ #include <sys/un.h>
+ #include <unistd.h>
// Protect callbacks for compatibility with light FFI
#ifdef Connection_decr_ref
end
# Spawned to manage a specific connection
-#
-# TODO, use polls
class Connection
super Writer
# A listener acting on an interface and port, spawns `Connection` on new connections
extern class ConnectionListener `{ struct evconnlistener * `}
- private new bind_to(base: NativeEventBase, address: CString, port: Int, factory: ConnectionFactory)
+ private new bind_tcp(base: NativeEventBase, address: CString, port: Int, factory: ConnectionFactory)
import ConnectionFactory.accept_connection, error_callback `{
- struct sockaddr_in sin;
- struct evconnlistener *listener;
ConnectionFactory_incr_ref(factory);
struct hostent *hostent = gethostbyname(address);
-
if (!hostent) {
return NULL;
}
- memset(&sin, 0, sizeof(sin));
+ struct sockaddr_in sin = {0};
sin.sin_family = hostent->h_addrtype;
sin.sin_port = htons(port);
memcpy( &(sin.sin_addr.s_addr), (const void*)hostent->h_addr, hostent->h_length );
- listener = evconnlistener_new_bind(base,
+ struct evconnlistener *listener = evconnlistener_new_bind(base,
(evconnlistener_cb)accept_connection_cb, factory,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
(struct sockaddr*)&sin, sizeof(sin));
+ if (listener != NULL) {
+ evconnlistener_set_error_cb(listener,
+ (evconnlistener_errorcb)ConnectionListener_error_callback);
+ }
+
+ return listener;
+ `}
+
+ private new bind_unix(base: NativeEventBase, file: CString, factory: ConnectionFactory)
+ import ConnectionFactory.accept_connection, error_callback `{
+
+ ConnectionFactory_incr_ref(factory);
+
+ struct sockaddr_un sun = {0};
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, file, sizeof(sun.sun_path) - 1);
+ struct evconnlistener *listener = evconnlistener_new_bind(base,
+ (evconnlistener_cb)accept_connection_cb, factory,
+ LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
+ (struct sockaddr*)&sun, sizeof(sun));
if (listener != NULL) {
- evconnlistener_set_error_cb(listener, (evconnlistener_errorcb)ConnectionListener_error_callback);
+ evconnlistener_set_error_cb(listener,
+ (evconnlistener_errorcb)ConnectionListener_error_callback);
}
return listener;
# Get the `NativeEventBase` associated to `self`
fun base: NativeEventBase `{ return evconnlistener_get_base(self); `}
- # Callback method on listening error
- fun error_callback do
+ # Callback on listening error
+ fun error_callback
+ do
var cstr = evutil_socket_error_to_string(evutil_socket_error)
- print_error "libevent error: '{cstr}'"
+ print_error "libevent error: {cstr}"
end
end
# Factory to listen on sockets and create new `Connection`
class ConnectionFactory
+
# The `NativeEventBase` for the dispatch loop of this factory
var event_base: NativeEventBase
return new Connection(buffer_event)
end
- # Listen on `address`:`port` for new connection, which will callback `spawn_connection`
- fun bind_to(address: String, port: Int): nullable ConnectionListener
+ # Listen on the TCP socket at `address`:`port` for new connections
+ #
+ # On new connections, libevent callbacks `spawn_connection`.
+ fun bind_tcp(address: String, port: Int): nullable ConnectionListener
do
- var listener = new ConnectionListener.bind_to(event_base, address.to_cstring, port, self)
+ var listener = new ConnectionListener.bind_tcp(
+ event_base, address.to_cstring, port, self)
+
if listener.address_is_null then
- sys.stderr.write "libevent warning: Opening {address}:{port} failed\n"
+ print_error "libevent warning: Opening {address}:{port} failed, " +
+ evutil_socket_error_to_string(evutil_socket_error).to_s
+ return null
end
+
return listener
end
- # Put string representation of source `address` into `buf`
+ # Listen on a UNIX domain socket for new connections
+ #
+ # On new connections, libevent callbacks `spawn_connection`.
+ fun bind_unix(path: String): nullable ConnectionListener
+ do
+ # Delete the socket if it already exists
+ var stat = path.file_stat
+ if stat != null and stat.is_sock then path.file_delete
+
+ var listener = new ConnectionListener.bind_unix(
+ event_base, path.to_cstring, self)
+
+ if listener.address_is_null then
+ print_error "libevent warning: Opening UNIX domain socket {path} failed, " +
+ evutil_socket_error_to_string(evutil_socket_error).to_s
+ return null
+ end
+
+ return listener
+ end
+
+ # Put a human readable string representation of `address` into `buf`
private fun addrin_to_address(address: Pointer, buf: CString, buf_len: Int): CString `{
struct sockaddr *addrin = (struct sockaddr*)address;
struct in6_addr *src = &((struct sockaddr_in6*)addrin)->sin6_addr;
return (char *)inet_ntop(addrin->sa_family, src, buf, buf_len);
}
+ else if (addrin->sa_family == AF_UNIX) {
+ struct sockaddr_un *src = (struct sockaddr_un*)addrin;
+ char *path = src->sun_path;
+ if (path == NULL) return "Unnamed UNIX domain socket";
+ if (path[0] == '\0') return "Abstract UNIX domain socket";
+ return path;
+ }
+
return NULL;
`}
end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Minimal usage example of libevent
+module libevent_example is example
+
+import libevent
+
+# Factory creating instances of `EchoConnection` to handle new connections
+class MyFactory
+ super ConnectionFactory
+
+ redef fun spawn_connection(buf, address)
+ do
+ return new EchoConnection(buf)
+ end
+end
+
+# Connection echoing data received from clients back at them
+class EchoConnection
+ super Connection
+
+ redef fun read_callback(content)
+ do
+ print "Received: {content}"
+ write content
+ end
+end
+
+# Skip the actual execution when testing
+if "NIT_TESTING".environ == "true" then exit 0
+
+# Setup libevent system
+var event_base = new NativeEventBase
+var factory = new MyFactory(event_base)
+
+# Open a TCP socket for listening
+factory.bind_tcp("localhost", 8888)
+
+# Open a UNIX domain socket for listening
+factory.bind_unix("/tmp/my.sck")
+
+# Launch event loop
+event_base.dispatch
+
+event_base.free
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module libevent_test
+
+import libevent
+import pthreads
+
+redef class Sys
+
+ var testing_id: Int is lazy do
+ var id = "NIT_TESTING_ID".environ
+ return if id.is_empty then 0 else id.to_i
+ end
+
+ # Config for test sockets
+ var tcp_addr = "localhost"
+ var tcp_port: Int = 20000 + testing_id
+ var unix_socket_path = "/tmp/libevent_test{testing_id}.sck"
+end
+
+class TestConnectionFactory
+ super ConnectionFactory
+
+ redef fun spawn_connection(buf, address)
+ do
+ print "[Server] New client: {address}"
+
+ var conn = new TestConnection(buf)
+ print "[Server] Write: Hi"
+ conn.write "Hi\n"
+ return conn
+ end
+end
+
+class TestConnection
+ super Connection
+
+ redef fun read_callback(content)
+ do
+ 0.2.sleep # Forcing the server output after the client output
+ printn "[Server] Read: {content}"
+ end
+end
+
+class ServerThread
+ super Thread
+
+ redef fun main
+ do
+ var event_base = new NativeEventBase
+ var factory = new TestConnectionFactory(event_base)
+
+ # Bind TCP socket
+ factory.bind_tcp(tcp_addr, tcp_port)
+
+ # Bind UNIX domain socket
+ factory.bind_unix unix_socket_path
+
+ event_base.dispatch
+ event_base.free
+
+ return null
+ end
+end
+
+redef fun system(cmd)
+do
+ if testing_id == 0 then print "[Client] {cmd}"
+ return super(cmd)
+end
+
+# First, launch a server in the background
+var server = new ServerThread
+server.start
+0.1.sleep
+
+# Test what should succeed
+system "echo 'Hello TCP' | nc -N {tcp_addr} {tcp_port}"
+system "echo 'Hello UNIX' | nc -NU {unix_socket_path}"
+
+1.0.sleep
+exit 0