Write CLIPS code that can talk to the internet!
- This repository is for educational purposes only :)
- I have only tested this codebase in Ubuntu 23.10 so far
- the library is only built for linux-based systems for now
- Don't know CLIPS? Try the Tour of CLIPS I wrote to learn!
Create a server that listens, accepts, and reads a message from a client:
CLIPS (Forge Alpha 5/12/24)
CLIPS> (create-socket AF_INET SOCK_STREAM)
3
CLIPS> (bind-socket 3 127.0.0.1 8889)
127.0.0.1:8889
CLIPS> (listen 3) ; NOTE: 127.0.0.1:8889 would work here, too
TRUE
CLIPS> (accept 3) ; NOTE: 127.0.0.1:8889 would work here, too
4
CLIPS> (get-socket-logical-name 4)
127.0.0.1:42616
CLIPS> (readline 127.0.0.1:42616)
A client that would connect to that server would look like this:
CLIPS (Forge Alpha 5/12/24)
CLIPS> (create-socket AF_INET SOCK_STREAM)
3
CLIPS> (connect 3 127.0.0.1 8889)
127.0.0.1:8889
CLIPS> (printout 127.0.0.1:8889 "Hello, server :)" crlf)
CLIPS> (flush-connection 3) ; NOTE: 127.0.0.1:8889 would work here, too
TRUEYou should see the message from the client in the previously mentioned server's rules engine when the client flushes their connection!
Provide low-level networking functions to CLIPS environments via socket file descriptors. Allows CLIPS-based applications to send and receive messages to/from other computers on a network via I/O Router1-based reading and writing operations.
This library adds functions that let you create network servers and clients. That is: you can make CLIPS applications that receive network requests and make network requests.
This library also supports DNS resolution. To discover IP addresses
for a given url, use (resolve-domain-name ryjo.codes) function.
Hint: Use (intersection$ (resolve-domain-name ryjo.codes))
if you see duplicate IP addresses returned.
Educational purposes for now. I would like to get the quality of code in this repo ready for upstream merging into CLIPS someday. For now, it's so I can make some fun web applications using CLIPS!
The project can be built from the root directory with make:
make
Note that this requires image magick header files on your system.
If you do not want it or otherwise do not need to use the (mimetype function
in your clips code, you can run:
make NO_IMAGE_MAGICK
This will create the binary clips file in the root directory.
Use this to run the example server and client network applications
provided by the files in the examples directory.
There are 4 example servers provided in this repository and 3 clients.
The simplest server
receives a single tcp connection and then exits.
The other can be used to serve multiple concurrent requests until it is
ctrl+z and killed.
./clips -f2 examples/server-simple.bat
or
./clips -f2 examples/server-complex.bat
./clips -f2 examples/client.bat
You should now also be able to connect to 127.0.0.1:8888 via telnet, curl, or a browser.
curl --http0.9 http://localhost:8888/
telnet localhost 8888
Visit http://localhost:8888 in the browser of your choice. The message displayed back is the first line of text that your browser sends to the webserver under the hood. This can be used to build web applications that let the users navigate to different "pages" in your web app. For example, if you try to go to http://localhost:8888/asdf-123, you'll see a slightly different message.
Looks up IP addresses given a domain name. Only tested with IPv4 and IPv6.
Accepts a connection on a socket file descriptor. Returns an integer representing the client's file descriptor or FALSE if it fails.
Binds a socket to a given IP/PORT or directory (in case of unix sockets). Returns an integer representing the client's file descriptor or FALSE if it fails.
Connects a socket to a given IP/PORT or directory (in case of unix sockets). Returns the logical name of the connection that can be read/written, or FALSE if it fails.
Closes a socket bound or connected on a given IP/PORT or directory (in case of unix sockets). Returns TRUE if connection closed successfully, FALSE if it fails.
Binds a socket to a given IP/PORT or directory (in case of unix sockets). Returns an integer representing the client's file descriptor or FALSE if it fails.
The following domains are currently supported:
AF_UNIX: used for unix socketsAF_INET: IPv4AF_INET6: IPv6
The following types are currently supported:
SOCK_STREAM: typically used for TCPSOCK_DGRAM: typically used for UDP
Protocol is optional and can typically be left blank.
Use this to "empty" the buffer of data received from the client. WARNING: If this is run on a blocking request, you may block indefinitely.
Returns errno, a global variable set when errors occur with some socket functions.
For example, if you try to bind twice on a socket:
$ ./clips
CLIPS (Forge Alpha 5/12/24)
CLIPS> (errno)
0
CLIPS> (errno-sym)
CLIPS> (create-socket AF_INET SOCK_STREAM)
3
CLIPS> (bind-socket 3 127.0.0.1 8889)
127.0.0.1:8889
CLIPS> (bind-socket 3 127.0.0.1 8887)
Could not bind 127.0.0.1
perror: Invalid argument
FALSE
CLIPS> (errno)
22
CLIPS> (errno-sym)
EINVAL
Add/remove certain flags on a socket as specified after the socket fd or logical name.
The following flags are currently supported:
O_NONBLOCKO_APPENDO_ASYNC
Flushes the buffer to the recipient.
Converts an integer representing a socket file descriptor to a symbol representing the name of the I/O router. Use this name to read and write to the socket.
Gets or Sets the timeout on the socket in microseconds.
Listens for connections on a socket. After running this,
you can now (accept clients connecting to your server
via the socket.
Polls the socket for a number of milliseconds for any given number of flags.
Specify 0 as ?milliseconds so that it returns immediately.
Possible flags:
POLLINPOLLOUTPOLLERRPOLLHUPPOLLNVALPOLLPRIPOLLRDNORMPOLLRDBANDPOLLWRNORMPOLLWRBAND
Returns TRUE if it got the FLAG, FALSE if it did not before timeout expires.
Gets or Sets options on the socket.
Possible values for ?level currently supported:
SOL_SOCKETIPPROTO_TCP
Possible values for ?optionName currently supported:
SO_REUSEADDRTCP_NODELAY
?value is an integer to set the flag to.
Changes the buffering style for a connection. As a means of example, stderr is "not buffered," stdout via your terminal is probably "line buffered," and files are normally "block buffered."
Shut down part or all of a full-duplex connection.
Possible values of ?optionalHow:
SHUT_RD: further receptions will be disallowedSHUT_WR: further transmissions will be disallowedSHUT_RDWR: further receptions and transmissions will be disallowed
Receives a single datagram from a socket.
This is primarily for UDP sockets (SOCK_DGRAM) but will also work with other datagram-style sockets like AF_UNIX.
Arguments:
?socketfdOrLogicalName: The socket to read from.
?flags (optional): Either a single symbol, integer, or a multifield of symbols
specifying flags for the underlying recvfrom call. Supported symbols:
MSG_PEEKMSG_OOBMSG_WAITALL
?maxlen (optional): Maximum number of bytes to read (defaults to 65535).
Must be greater than 0 and no more than 65536.
Return Value:
A multifield with up to 6 elements, depending on the socket family:
- Address Family (ex
AF_INET) - Address / Path (ex
127.0.0.1or/tmp/foo) - Port (or 0 for UNIX) (ex
9999) - Bytes received (ex
12) - Data received (ex "Hello world")
Example:
; Simple UDP receive
(bind ?mf (recvfrom ?sock))
(printout t "Got " (nth$ 4 ?mf) " bytes: " (nth$ 5 ?mf) crlf)
; With flags
(bind ?mf (recvfrom ?sock (create$ MSG_PEEK MSG_WAITALL) 512))Sends a single datagram to a destination.
This is primarily for UDP sockets (SOCK_DGRAM).
Arguments:
?socketfdOrLogicalName: The socket to send from.
?family: The address family of the destination:
AF_UNIX: UNIX domain socketsAF_INET: IPv4AF_INET6: IPv6
?address: Destination address:
- Path string for AF_UNIX
- IP address string for AF_INET/AF_INET6
?port: Destination port (required for AF_INET/AF_INET6, ignored for AF_UNIX).
?data: The string data to send. Can be empty ("") for signaling or keepalive packets.
?flags (optional): Either a single symbol, integer, or a multifield of symbols
specifying flags for the underlying sendto call. Supported symbols:
MSG_CONFIRMMSG_DONTROUTEMSG_DONTWAITMSG_EORMSG_MOREMSG_NOSIGNALMSG_OOB
Return Value:
Returns the number of bytes successfully sent as an integer,
or FALSE if an error occurred.
Use (errno) and (errno-sym) to get details.
Examples:
; Send a message to a UNIX domain socket
(sendto ?sock AF_UNIX "/tmp/socket" "hello")
; Send a UDP datagram to IPv4 address
(sendto ?sock AF_INET "127.0.0.1" 9999 "ping")
; Send with multiple flags
(sendto ?sock AF_INET "192.168.1.100" 12345 "payload" (create$ MSG_NOSIGNAL MSG_DONTWAIT))In order to watch all activity on your computer's port 8888
(which the example server and client use by default),
use tcpdump. You may need to run as root or with sudo:
tcpdump -nn -i any port 8888
This codebase is based on
CLIPS 7.0.x
released on 5/12/24. I added a socketrtr.h and socketrtr.c to support reading/writing to sockets.
I add user defined functions (UDFs) to CLIPS environments compiled with this source code
in userfunctions.c. I initialize the socket router in router.c
inside of the function InitializeDefaultRouters.
Footnotes
-
See Section 9: I/O Routers of the CLIPS Advanced Programming Guide ↩