Chapter 5. I/O

Table of Contents

Ports
URLs
Buffered I/O
Character Ports
String Ports
Binary Ports and Block IO
Java Ports
Serialization
Networking
IP Addressing
Socket Operations
TCP
TLS and SSL
UDP
Multicast UDP
User Defined Ports
Miscellaneous
Pretty-Printing
Source Loading
Location Tracking
Locating Resources
File Manipulation
Class Loading

SISC's I/O routines are implemented in a flexible manner, allowing extensions to create new I/O sources that will behave as standard Scheme port objects. The ports can then be operated on with all R5RS port operations, as well as some SISC specific port functions.

Ports

URLs

In SISC all procedures that create ports for accessing files, e.g. open-input-file, open-output-file accept URLs in addition to ordinary file names. Here are some examples of valid URLs:

http://foo.com/bar/bar1.scm
file:/tmp/foo.scm
file:c:\bar\baz.scm
file:foo.scm
jar:http://foo.com/bar.jar!/bar/bar1.scm
        

The last is a URL referring to a file stored in a JAR on a remote web server. For further details on the format of URLs please consult this specification. The format of JAR URLs is defined in the JDK API documentation. What types of URLs are supported by a particular installation of Java depends on the configured protocol handlers. See the JDK API documentation for details. [3]

Relative file names or URLs are resolved in relation to the following parameter:

parameter: (current-url [url]) => url

Retrieves or sets the URL which forms the basis for resolving relative filenames and URLs. It is initialized on start up with the path to the current directory. All parameters and the returned value are strings.

The algorithm for resolving relative URLs is defined in this specification. For compatibility with other Schemes, SISC also supports the current-directory procedure, which is a simple wrapper around current-url.

A convenience procedure exists for executing a procedure while the current-url is temporarily set to a different value:

procedure: (with-current-url url thunk) => value

Sets the current URL to the URL obtained by normalizing url in relation to the current URL, then executes thunk, and then sets the current URL back to the previous value.

URLs can be normalized using

procedure: (normalize-url url1 [url2]) => url

When called with one string argument, normalize-url returns the normalized version of the given URL. Normalization involves, amongst other things, the replacement of relative path references such as . and ...

When called with two string arguments, the procedure returns the normalized version of the second URL when interpreted as a being relative to the first URL.

Buffered I/O

Buffered ports are provided in SISC to layer over any existing port, and read or write in larger, more efficient chunks. Buffered ports are created with the following constructors, which accept the underlying port and an optional size, indicating the number of bytes or characters to buffer before making an underlying read or write.

procedure: (open-buffered-binary-input-port binary-input-port) => binary-input-port

Creates a binary-input-port which is buffered, and reads its bytes from the provided port.

procedure: (open-buffered-binary-output-port binary-output-port) => binary-output-port

Creates a binary-output-port which is buffered, and writes its bytes to the provided port.

procedure: (open-buffered-character-input-port character-input-port) => character-input-port

Creates a character-input-port which is buffered, and reads its characters from the provided port.

procedure: (open-buffered-character-output-port character-output-port) => character-output-port

Creates a character-output-port which is buffered, and writes its characters to the provided port.

With buffered output ports, individual writes may not actually reach the eventual output source, so the programmer must explicitly flush the port when it the output data must reach its destination.

procedure: (flush-output-port [output-port]) => undefined

Causes the specified output-port's buffered data to be written immediately. This operation is allowed on any output port, but may have no affect on some. output-port defaults to current-output-port.

Character Ports

The R5RS I/O primitives implemented by SISC create character ports. Character ports read characters from input sources and treat the data as characters in a given character set. Correspondingly, character ports output bytes from characters according to a given character set's encoding rules.

By default character ports use the value of the string parameter character-set as the character encoding name. A list of many possible encoding names can be found in the Java Platform Documentation

One may temporarily change the default character set using the with-character-set function.

procedure: (with-character-set encoding thunk) => value

Changes the value of the character-set parameter, and the default character set to the named encoding while executing the body of thunk.

Port Creation

procedure: (open-input-file url [encoding]) => input-port

Creates an input port from the specified url. If the optional encoding parameter, a string, is supplied input will be decoded from the specified encoding rather than the default.

procedure: (open-output-file url [encoding] [auto-flush]) => output-port

Creates an output port to the specified url. If the optional encoding parameter, a string, is supplied output will be encoded in the specified encoding rather than the default. If the optional auto-flush argument is provided and is non-false, the port will automatically flush after each write call. If the specified file exists, it will be overwritten silently when the port is opened.

procedure: (open-character-input-port binary-input-port [character-set]) => input-port

Creates an input port which reads from the provided binary input port. One may specify the desired character set as a string, otherwise the character set is retrieved from the character-set dynamic parameter.

procedure: (open-character-output-port binary-output-port [character-set] [auto-flush]) => output-port

Creates an output port which writes to the provided binary output port. One may specify the desired character set as a string, otherwise the character set is retrieved from the character-set dynamic parameter. If the optional auto-flush argument is provided and is non-false, the port will automatically flush after each write call.

Bulk Character I/O

In addition to the R5RS I/O primitives, SISC provides two functions for reading and writing blocks of characters from a character port.

procedure: (read-string buffer offset count [character-input-port]) => integer

Reads up to count characters from the implicit or specified character port into the string buffer starting from the position specified by the integer offset. The number of characters successfully read (which may be fewer than count) is returned, or the end-of-file value if the end-of-file was reached before any characters were encountered.

procedure: (write-string buffer offset count [character-output-port]) => undefined

Writes exactly count characters from the given string buffer starting from the integer position offset to the implicit or specified character output port.

Port Creation Wrappers

The next set of procedures assists in creating a port, followed by calling a given procedure with that port. When the procedure returns, the port is closed. Invoking escaping continuations from inside the procedure does not close the port, and invoking a continuation captured inside the procedure does not open the port.

procedure: (call-with-input-file url [encoding] procedure) => value

Calls procedure with a new input port attached to url. The result of the thunk is returned.

If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

procedure: (call-with-output-file url [encoding] procedure) => value

Calls procedure with a new character output port attached to url. The result of the thunk is returned.

If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

Replacing Standard Ports

The following procedures wrap a thunk, redirecting the input and output of the thunk while it is evaluating to an input or output port other than the current-input-port and current-output-port. Invoking escaping continuations from inside the procedure restores the original port, and invoking a continuation captured inside the procedure restores the redirection.

procedure: (with-input-from-port input-port thunk) => value

Evaluates thunk with input-port as the current-input-port for the duration of the evaluation.

procedure: (with-output-to-port output-port thunk) => value

Evaluates thunk with output-port as the current-output-port for the duration of the evaluation.

procedure: (with-input-from-file url [encoding] thunk) => value

Evaluates thunk with an input port attached to a file opened for reading from url as the current-input-port for the duration of the evaluation. The port is closed when thunk returns normally.

If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

procedure: (with-output-to-file url [encoding] thunk) => value

Evaluates thunk with an input port attached to a file opened for writing to url as the current-output-port for the duration of the evaluation. The port is closed when thunk returns normally.

If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

Port Predicates

procedure: (input-port? value) => #t/#f

Returns #t if value is an input port, #f otherwise.

procedure: (output-port? value) => #t/#f

Returns #t if value is an output port, #f otherwise.

String Ports

Requires: (import string-io)

String ports are input or output ports that read or write to a string rather than a file or other stream. String ports can be used to parse or emit formatted strings using the standard Scheme port operations. A String Input port will read from a given string until the end of string is reached, at which point #!eof is returned.

String ports deal with characters as the atomic unit, and as such preserve full unicode width characters at all times.

procedure: (open-input-string string) => string-input-port

Creates a string input port whose characters are read from the provided string. Characters will be returned from any read operation on the port until the end of the string is reached. Read calls after reaching the end of the string will return #!eof.

procedure: (open-output-string) => string-output-port

Creates a string output port, which behaves as an ordinary output port, except that writes are used to create a string as output. The results of all the write operations are retrieved using get-output-string.

procedure: (get-output-string string-output-port) => string

Returns the string that was created by zero or more writes to a string output port. If no writes were performed on the string output port, an empty string ("") is returned. After this call, the provided string output port is reset to its initial, empty state.

procedure: (call-with-input-string string procedure) => value

Calls procedure with a new string input port created from string. The result of the thunk is returned.

procedure: (call-with-output-string procedure) => string

Calls procedure with a new string output port. The contents of the string-output-port are returned when the procedure returns.

procedure: (with-input-from-string string thunk) => value

Evaluates thunk with a string-input-port created from string as the current-input-port for the duration of the evaluation.

procedure: (with-output-to-string thunk) => string

Evaluates thunk with a string-output-port created as the current-output-port for the duration of the evaluation. When the thunk returns, the contents of the string-output-port are returned.

procedure: (string-input-port? value) => #t/#f

Returns #t if value is a string input port, #f otherwise.

procedure: (string-output-port? value) => #t/#f

Returns #t if value is a string output port, #f otherwise.

Note

This interface complies with SRFI-6 (Basic String Ports).

Binary Ports and Block IO

Requires: (import binary-io)

In addition to the R5RS I/O functions, SISC provides a symmetric set of functions for reading and writing binary data to and from ports with no character set translation. These ports are operated on using the binary I/O functions described below. Using character-oriented operations (such as the traditional R5RS functions read, read-char, display, etc.) is an error. Binary ports also provide block input/output functions, that allow a Scheme program to read blocks of more than one byte of data at a time from binary ports. SISC stores data that is read or is to be written in block fashion in a binary buffer (see the section called “Binary Buffers”).

procedure: (open-binary-input-file url) => binary-input-port

Creates an input port in the same manner as R5RS open-input-file, producing an input port that does no character-set decoding on the bytes read as input.

procedure: (open-binary-output-file url [auto-flush]) => binary-output-port

Creates an output port in the same manner as open-output-file, producing an output port that does no character-set encoding.

procedure: (call-with-binary-input-file url procedure) => value

Calls procedure with a new binary input port attached to url. The result of the thunk is returned.

procedure: (call-with-binary-output-file url procedure) => value

Calls procedure with a new binary output port attached to url. The result of the thunk is returned.

procedure: (with-binary-input-from-file url thunk) => value

Evaluates thunk with a binary input port attached to a file opened for reading from url as the current-input-port for the duration of the evaluation. The port is closed when thunk returns normally.

procedure: (with-binary-output-to-file url thunk) => value

Evaluates thunk with a binary output port attached to a file opened for writing to url as the current-output-port for the duration of the evaluation. The port is closed when thunk returns normally.

There are several operations specifically available for use on binary ports.

procedure: (peek-byte [binary-input-port]) => integer

Similar to peek-char, reads ahead one byte in the stream, returning the next byte available but not advancing the stream. The byte is returned as an integer. The current input port is used unless specified.

procedure: (read-byte [binary-input-port]) => integer

Reads a single byte from the stream, advancing the stream and returning the byte as an integer. The current input port is used unless specified.

procedure: (read-block buffer offset count [binary-input-port]) => integer

Reads up to count bytes of data from the current input port or the binary-input-port parameter if provided, into the binary buffer buffer starting at position offset. Note that less than count bytes may be read. The number of bytes actually read is returned. If the end-of-file is encountered before any bytes could be read, #!eof will be returned.

procedure: (write-byte integer [binary-output-port]) => undefined

Writes a single byte specified as an integer to the given binary-output-port if provided, current output port otherwise.

procedure: (write-block buffer offset count [binary-output-port]) => undefined

Writes count bytes of data from the provided buffer at starting point offset to the given binary-output-port or to the current output port if unspecified. Exactly count bytes will be written.

procedure: (binary-input-port? value) => #t/#f

Returns #t if value is a binary input port, #f otherwise.

procedure: (binary-output-port? value) => #t/#f

Returns #t if value is a binary output port, #f otherwise.

Buffer I/O

Requires: (import buffer-io)

A module is also provided for input and output to and from binary buffers using buffer ports, similar to character I/O to and from strings using string ports.

procedure: (open-input-buffer buffer) => buffer-input-port

Creates a buffer input port whose bytes are read from the provided buffer. Bytes will be returned from any read operation on the port until the end of the buffer is reached. Read calls after reaching the end of the buffer will return #!eof.

procedure: (open-output-buffer) => buffer-output-port

Creates a buffer output port, which behaves as an ordinary output port, except that writes are used to create a buffer as output. The results of all the write operations are retrieved using get-output-buffer.

procedure: (get-output-buffer buffer-output-port) => buffer

Returns the buffer that was created by zero or more writes to a buffer output port. If no writes were performed on the buffer output port, an empty buffer is returned. After this call, the provided buffer output port is reset to its initial, empty state.

procedure: (call-with-input-buffer buffer procedure) => value

Calls procedure with a new buffer input port created from buffer. The result of the thunk is returned.

procedure: (call-with-output-buffer procedure) => buffer

Calls procedure with a new buffer output port. The contents of the buffer-output-port are returned when the procedure returns.

procedure: (with-input-from-buffer buffer thunk) => value

Evaluates thunk with a buffer-input-port created from buffer as the current-input-port for the duration of the evaluation.

procedure: (with-output-to-buffer thunk) => buffer

Evaluates thunk with a buffer-output-port created as the current-output-port for the duration of the evaluation. When the thunk returns, the contents of the buffer-output-port are returned.

Java Ports

Requires: (import java-io)

This module provides procedures to convert between Scheme and Java I/O types. In general, binary Scheme ports map to plain Java streams, while character Scheme ports map to Java readers and writers.

The following example is somewhat contrived, but illustrates a common usage pattern:

          
;; A convoluted Hello World example.
(import s2j)
(import java-io)

(define-java-classes
  (<java.lang.system> |java.lang.System|))

(define-generic-java-field-accessors
  (:jout out))

(let ((stdout (open-character-output-port
               (->binary-output-port
                (:jout (java-null <java.lang.system>)) #t) #t)))
  (display "Hello, world!" stdout))
          
        

procedure: (->binary-input-port jinput-stream) => binary-input-port

Returns a binary input port associated to the java.io.InputStream object passed as the jinput-stream parameter.

procedure: (->binary-output-port joutput-stream [aflush?]) => binary-output-port

Returns a binary input port associated to the java.io.OutputStream object passed as the joutput-stream parameter. If the optional boolean aflush? parameter is not provided, the port will not autoflush by default.

procedure: (->character-input-port jreader) => character-input-port

Returns a character input port associated to the java.io.Reader object passed as the jreader parameter.

procedure: (->character-output-port jwriter [aflush?]) => character-output-port

Returns a binary input port associated to the java.io.Writer object passed as the joutput-stream parameter. If the optional boolean aflush? parameter is not provided, the port will not autoflush by default.

procedure: (->jinput-stream input-port) => jinput-stream

Returns a java.io.InputStream object associated to the given Scheme input-port. The function produces an error if a character port is passed.

procedure: (->joutput-stream output-port) => joutput-stream

Returns a java.io.OutputStream object associated to the given Scheme output-port. The function produces an error if a character port is passed.

procedure: (->jreader character-input-port) => jreader

Returns a java.io.Reader object associated to the given Scheme character-input-port.

procedure: (->jwriter character-output-port) => jwriter

Returns a java.io.Writer object associated to the given Scheme character-output-port.

Scheme ports can also be used from Java. See the section called “Scheme I/O” for information.

Serialization

Requires: (import serial-io)

With read and write, Scheme values are read and written in a standardized, textual external representation. However, this external representation only fully describes a limited subset of Scheme types. For instance it is impossible to read/write a procedure, or closure, or continuation.

SISC provides a special composed port type and procedures for reading and writing any Scheme value using a binary representation. The (de)serialization preserves the referential structure of the object graph comprising the serialized values.

Serial ports are composed onto and thus are binary ports, i.e. all operations applicable to binary ports also apply to serial ports.

Port Creation and Identification

procedure: (open-serial-input-port binary-input-port) => serial-input-port

Creates a serial input port which reads external representations of Scheme values from the given binary input port.

procedure: (open-serial-output-port binary-output-port [auto-flush]) => serial-output-port

Creates a serial output port which can be used to write external representations of Scheme values to the provided binary output port.

procedure: (serial-input-port? value) => #t/#f

Returns #t if value is a serial input port, #f otherwise.

procedure: (serial-output-port? value) => #t/#f

Returns #t if value is a serial output port, #f otherwise.

Serial Port Wrappers

procedure: (call-with-serial-input-port binary-input-port procedure) => value

Calls procedure with a new serial input port attached to binary-input-port. The result of the thunk is returned.

procedure: (call-with-serial-output-port binary-output-port procedure) => value

Calls procedure with a new serial output port attached to binary-output-port. The result of the thunk is returned.

procedure: (with-serial-input-from-port binary-input-port thunk) => value

Evaluates thunk with a serial input port attached to the specified binary-input-port as the current-input-port for the duration of the evaluation. The port is not closed when thunk returns.

procedure: (with-serial-output-to-port binary-output-port thunk) => value

Evaluates thunk with a serial input port attached to the specified binary-output-port as the current-output-port for the duration of the evaluation. The port is not closed when thunk returns.

procedure: (call-with-serial-input-file url procedure) => value

Calls procedure with a new serial input port attached to url. The result of the thunk is returned.

procedure: (call-with-serial-output-file url procedure) => value

Calls procedure with a new serial output port attached to url. The result of the thunk is returned.

procedure: (with-serial-input-from-file url thunk) => value

Evaluates thunk with a serial input port attached to a file opened for reading from url as the current-input-port for the duration of the evaluation. The port is closed when thunk returns normally.

procedure: (with-serial-output-to-file url thunk) => value

Evaluates thunk with a serial input port attached to a file opened for writing to url as the current-output-port for the duration of the evaluation. The port is closed when thunk returns normally.

Serialization Procedures

procedure: (deserialize [serial-input-port]) => value

Reads a Scheme value from an external representation retrieved from serial-input-port. If serial-input-port is absent the data is read from the current input port.

procedure: (serialize value [serial-output-port]) => undefined

Writes an external representation of value to serial-output-port. If serial-output-port is absent the data is written to the current output port.

Networking

Requires: (import networking)

The SISC Networking library provides a mechanism for creating and manipulating IP network protocols as standard Scheme ports. SISC supports TCP, UDP, and Multicast UDP. Each is described in the sections that follow.

Each protocol provides one or more socket constructors. These functions produce a Socket handle, which is represented in SISC as #<socket>. A socket handle is then used to obtain Scheme ports.

IP addresses and network hostnames are represented as strings in SISC. Unless otherwise noted, the network library functions that require an address may take a network address as a string which may be any of:

  • A hostname, to be resolved through the domain name system.

  • An IPv4 network address in the standard dotted quad format. (RFC-791)

  • An IPv6 network address in colon separated hexadecimal form, and zero-shortened form. (RFC-2373)

IPv6 addresses must be supported by the underlying operating system. An error may be raised if the address is not supported. All IP port values must be exact integers in the proper range.

IP Addressing

Several utility functions are provided for manipulating IP addresses. These are described below.

procedure: (get-host-ip-by-name hostname) => string

Attempts to resolve a hostname provided as a string into an IP address in dotted-quad form. If the host cannot be found, #f is returned.

procedure: (get-host-name-by-ip ip-address) => string

Attempts a reverse lookup of the given dotted-quad address to determine a registered domain name. If unsuccessful, #f is returned.

procedure: (get-local-host) => string

Attempts to determine the Internet visible IP address of the local machine. If successful, this address is returned in dotted-quad notation. #f is returned otherwise.

Socket Operations

Once obtained using a protocol specific constructor, a Socket Handle allows manipulation of common socket options, the creation of Scheme input/output ports, and closing of the socket.

procedure: (socket? value) => #t/#f

Returns true if and only if the provided value is a socket.

procedure: (open-socket-input-port socket [encoding]) => input-port

Opens a character input port to the socket. If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

procedure: (open-socket-output-port socket [encoding] [auto-flush]) => output-port

Opens a character output port to the socket. If provided, the boolean argument specifies whether the given port should be set to auto-flush mode. If unspecified, the port does not auto-flush. If the optional encoding parameter is provided, the character port created will use the specified encoding rather than the default.

procedure: (open-binary-socket-input-port socket) => binary-input-port

Opens a binary input port to the socket.

procedure: (open-binary-socket-output-port socket [auto-flush]) => binary-output-port

Opens a character output port to the socket. If provided, the boolean argument specifies whether the given port should be set to auto-flush mode. If unspecified, the port does not auto-flush.

procedure: (close-socket socket) => unspecified

Closes an IP socket.

The port-obtaining functions above work on most sockets. An exception applies for TCP server sockets, which are used only to obtain connected TCP sockets.

TCP

The most commonly used Internet protocol maps most favorably to Scheme's input/output model. Writing to an output port retrieved from a TCP socket writes the data to that socket. Reading from an input port reads from the connected socket. One important note is that one can control the amount of data that fills a TCP packet by using an output port that does not auto-flush. Data is written to the port until one considers the packet complete, and then uses (flush-output-port port) to complete the packet. Note also that this does not guarantee that one gets the desired packet size, but does allow one to construct reasonably sized packets.

TCP sockets are obtained one of two ways. Either one creates an outgoing connection to another listening host and then subsequently obtains a socket handle, or one creates a listening socket and then obtains a socket by waiting for an incoming connection on the specified port. In either case, the result is a socket handle with an available input and output port that can be obtained using a function in the previous section.

procedure: (open-tcp-socket host port) => socket

Attempts to connect to the host at the given hostname or IP address encoded as a string, at the given TCP port specified as an integer. An error is raised if the host cannot be found or the connection fails. If successful, a socket is returned.

procedure: (open-tcp-listener port [interface-address]) => server-socket

Creates a TCP server socket, which may only be used with accept-tcp-socket, or closed. The server socket will listen on the integer port specified. If provided, the interface-address, a string specifies the address of a local interface to bind to. If not provided, the port is bound on all available interfaces. An error is raised if the socket cannot be bound and set listening.

procedure: (server-socket? value) => #t/#f

Returns true if and only if the provided value is a server socket.

procedure: (accept-tcp-socket server-socket) => socket

Accepts an incoming connection on the provided server-socket, and returns a TCP socket handle. This function will block until an incoming connection is made, or, if set, the socket timeout is exceeded. If the latter happens, an error will be raised.

procedure: (set-so-timeout! socket timeout) => undefined

Sets the socket timeout on a socket. The socket can be either a server socket or connected socket. In the former case, this value specifies the number of milliseconds that an accept-tcp-socket can wait before timing out. In the latter, the value specifies the number of milliseconds that can elapse during a read call before timing out.

TLS and SSL

Sockets which are encrypted using the Secure Sockets Layer (SSL) or Transport Security Layer (TLS) can be created an accepted as well. This functionality can heavily depend on the underlying support in the JVM, including security settings, restrictions, installed certificates, etc. For information on the Java Secure Socket Extension (JSSE) and its setup, refer to the JSSE documentation page at Sun.

The SSL functionality is built atop the sockets and server sockets functionality as in TCP networking above. A separate constructor for sockets (open-ssl-socket) and listening sockets (open-ssl-listener) exist, and produce sockets which are used identically to TCP sockets as previously described.

procedure: (open-ssl-socket host port [auto-close]) => socket

Attempts to connect to the host at the given hostname or IP address encoded as a string, at the given TCP port specified as an integer, and establish an SSL connection using the default cipher suites and protocols available. An error is raised if the host cannot be found or the connection fails. If successful, a socket is returned.

procedure: (open-ssl-listener port [interface-address]) => server-socket

Creates an SSL TCP server socket, which may only be used with accept-tcp-socket, or closed. The server socket will listen on the integer port specified. If provided, the interface-address, a string specifies the address of a local interface to bind to. If not provided, the port is bound on all available interfaces. An error is raised if the socket cannot be bound and set listening.

The cipher suites, protocols, and modes which this server socket will accept are set with the following functions.

procedure: (get-enabled-cipher-suites ssl-server-socket) => list

Returns a list of strings naming the cipher suites which are enabled for this server socket.

procedure: (set-enabled-cipher-suites! ssl-server-socket suite-list) => list

Accepts an ssl server socket and a list of strings naming the cipher suites which should be available for negotiation with the remote end. The previous list is returned.

procedure: (get-enabled-protocols ssl-server-socket) => list

Returns a list of strings naming the security protocols which are enabled for this server socket.

procedure: (set-enabled-protocols! ssl-server-socket protocol-list) => list

Accepts an ssl server socket and a list of strings naming the protocols which should be available for negotiation with the remote end. The previous list is returned.

procedure: (session-creation-permitted? ssl-server-socket) => boolean

Returns true if new SSL/TLS sessions can be created by the sockets obtained from this server socket.

procedure: (set-session-creation-permitted! ssl-server-socket boolean) => boolean

Sets whether new SSL/TLS sessions can be created by the sockets obtained from this server socket. The previous value is returned.

procedure: (is-client-mode? ssl-server-socket) => boolean

Returns true if the SSL server socket will be in the rare client mode after accepting the connection, rather than the more common server mode.

procedure: (set-client-mode! ssl-server-socket boolean) => boolean

Sets whether the SSL server socket will be in client mode when accepting new connections. The previousvalue is returned.

procedure: (get-client-auth ssl-server-socket) => symbol or #f

Returns the necessity of the remote client to authenticate to this server socket. The returned values are either needed, indicating the remote must authenticate, wanted, indicating the remote will be requiested to authenticate but is not required to, or #f, indicating the remote will not be requested to authenticate.

procedure: (set-client-auth! ssl-server-socket client-auth-mode) => symbol or #f

Sets the client authentication requirements, as described in get-client-auth above. One of the above values must be specified. The previous value is returned.

UDP

UDP sockets can be obtained for both receive only and send/receive sessions.

Note

The behavior of the char-ready? function is somewhat more difficult to predict on a UDP input port. The function will return #t only when a previous datagram contained more bytes than were requested by the read operation that received it.

procedure: (open-udp-listen-socket listen-port [interface-address] [datagram-size]) => udp socket

Opens a UDP socket that listens on listen-port (optionally bound to only the interface on interface-address). If provided, datagram-size specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than datagram-size are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

procedure: (open-udp-socket remote-host remote-port) => udp socket

Opens a UDP socket for sending datagrams to the Internet host specified by remote-host, on port remote-port.

After obtaining a UDP socket, input and output ports can be obtained in the usual manner. It is an error to attempt to obtain an output-port from a listening UDP socket, or an input port from a sending UDP socket.

UDP input ports behave as ordinary input ports. When a datagram arrives as a result of any read operation on the port, their entire contents are stored in a buffer of length datagram-size bytes. Successive read operations return data from that buffer until it is exhausted, at which point a read operation will cause the UDP socket to listen for another datagram.

UDP output ports should be treated with some care, however. If a UDP output port was obtained in auto-flush mode, each write operation to the output port will cause a new datagram to be sent. Control over the size of the datagram must be maintained by using a port that does not auto-flush, writing the desired data, and flushing once the amount of data that the user wants to occupy a single UDP datagram is reached. The behavior of constructing very large UDP packets is undefined. The packet may be silently dropped or (more likely) fragmented at the IP layer.

Multicast UDP

SISC provides support for IP multicast UDP datagrams as well. This allows a program to both send and receive to an IP multicast group. Multicast UDPs are an extension of ordinary UDP. Thus all I/O operations on a Multicast UDP socket are subject to the same semantics as an ordinary UDP socket.

Note

The Multicast UDP library requires that the underlying operating system's IP networking stack support Multicast. The functions described here may produce an error if the operating system does not.

A program wishing to use multicast UDP sockets must first obtain a multicast socket for either listening to a multicast group, or for both listening and sending to such a group.

procedure: (open-multicast-socket listen-port [interface-address] [datagram-size]) => multicast udp socket

Opens a multicast UDP socket that listens on listen-port (optionally bound only to the interface addressed by interface-address. If provided, datagram-size specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than datagram-size are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

procedure: (open-multicast-socket group port [interface-address] [datagram-size]) => multicast udp socket

Opens a multicast UDP socket for sending datagrams to the specified multicast group, on the specified port. The returned socket will also be capable of listening to that group on the same port (and optionally bound only to the interface addressed by interface-address), though the socket will not initially be a member of the group. If provided, datagram-size specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than datagram-size are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

Once a sending socket has been obtained (the second form), an output-port can be obtained in the usual manner, and datagrams can be immediately sent to the multicast group. To receive datagrams, sockets returned from both forms must join a multicast group.

A multicast group is specified by a class D IP address and by a standard UDP port number. Class D IP addresses are in the range 224.0.0.0 to 239.255.255.255, inclusive. The address 224.0.0.0 is reserved and should not be used.

Groups are joined and left using the following functions:

procedure: (join-multicast-group multicast-socket group) => undefined

Causes the given multicast socket to join the group specified by the Internet address in group. Once joined, read operations on an obtained input-port will be able to receive datagrams destined to that group.

procedure: (leave-multicast-group multicast-socket group) => undefined

Causes the given multicast socket to leave the group specified by the Internet address in group. Read operations on any input ports obtained from this socket will no longer receive datagrams from that group.

A single multicast socket can simultaneously listen to more than one multicast group. A socket can only send to one group, however: the group it was constructed with.

Multicast packets are limited in extent by their time-to-live. Each time a multicast packet crosses a router, its ttl is decremented. In this manner, one can send datagrams only to local networks or subnetworks, as well as more grand scopes. The TTL of a socket is set using set-multicast-ttl!

procedure: (set-multicast-ttl! multicast-socket ttl) => undefined

Sets the multicast TTL of the given socket to ttl, an integer. All datagrams sent after this call will have their TTL set to the new value.

Valid multicast TTLs are in the range 0 (restricted to the same host) to 255 (unlimited in scope).

User Defined Ports

Requires: (import custom-io)

SISC provides the ability to create new I/O port types from within Scheme. To add a new port type, one invokes a custom port constructors described below, passing procedures (Scheme or otherwise) which implement the operations required by that port. Each custom port constructor returns a Scheme port which may be used with any of the SISC or R5RS I/O functions. Port implementors may also use an associated port-local value to coordinate state for some specialized ports.

procedure: (set-port-local! custom-port value) => value

Sets the port-local value of the given custom port.

procedure: (port-local wrapper-stream) => value

Gets the port-local value of the given custom port.

procedure: (make-custom-character-input-port read read-string ready? close) => character-input-port

Creates a character-input-port whose functionality is implemented by the four provided fundamental procedures.

The fundamental procedures required are as follows:

(read port) => integer

Return a character as an integer value, or -1 if the end of stream has been reached. port is a reference to the custom Scheme port which contains the procedure.

(read-string port mutable-string offset count) => integer

Reads up to count characters into the given mutable string at position offset. The number of actual characters read are returned as an integer value, or -1 if the end of stream has been reached. port is a reference to the custom Scheme port which contains the procedure.

(ready? port) => boolean

Returns a non-false value if one or more characters are available for reading, false otherwise.

(close port) => undefined

Closes the port.

procedure: (make-custom-binary-input-port read read-block available close) => binary-input-port

Creates a binary-input-port whose functionality is implemented by the four provided fundamental procedures.

The fundamental procedures required are as follows:

(read port) => integer

Return a byte as an integer value, or -1 if the end of stream has been reached. port is a reference to the custom Scheme port which contains the procedure.

(read-block port buffer offset count) => integer

Reads up to count bytes into the given binary buffer at position offset. The number of actual bytes read are returned as an integer value, or -1 if the end of stream has been reached. port is a reference to the custom Scheme port which contains the procedure.

(available port) => integer

Returns a count of the number of bytes which are available for reading. This number need not be accurate.

(close port) => undefined

Closes the port.

procedure: (make-custom-character-output-port write write-string flush close) => character-output-port

Creates a character-output-port whose functionality is implemented by the four provided fundamental procedures.

The fundamental procedures required are as follows:

(write port byte) => undefined

Writes a character represented as an integer value. port is a reference to the custom Scheme port which contains the procedure.

(write-string port string offset count) => void

Writes count characters from the given string at position offset. port is a reference to the custom Scheme port which contains the procedure.

(flush port) => undefined

Flushes any unwritten characters to the stream.

(close port) => undefined

Closes the port.

procedure: (make-custom-binary-output-port write write-block flush close) => binary-output-port

Creates a binary-output-port whose functionality is implemented by the four provided fundamental procedures.

The fundamental procedures required are as follows:

The fundamental procedures required are as follows:

(write port byte) => undefined

Writes a byte represented as an integer value. port is a reference to the custom Scheme port which contains the procedure.

(write-block port buffer offset count) => void

Writes count bytes from the given binary buffer at position offset. port is a reference to the custom Scheme port which contains the procedure.

(flush port) => undefined

Flushes any unwritten characters to the stream.

(close port) => undefined

Closes the port.

Finally, the underlying procedures which implement a custom port can be retrieved with the following function:

procedure: (custom-port-procedures custom-port) => list

Returns the procedures which implement a custom port as a list.

As an example, here are String Ports, implemented as user defined ports:

Example 5.1. User defined string ports

(import custom-io)

;; String Input Ports

(define (sio/read port)
  (let ([local (port-local port)])
    (let ([ptr (vector-ref local 1)])
      (if (= (vector-ref local 2) ptr)
          -1
          (let ([c (char->integer
                    (string-ref (vector-ref local 0) ptr))])
            (vector-set! local 1 (+ ptr 1))
            c)))))

(define (sio/read-string local buffer offset length)
  (let ([local (port-local port)])
    (let ([str (vector-ref local 0)]
          [strlen (vector-ref local 2)])
      (do ([i offset (+ i 1)]
           [j (vector-ref local 1) (+ j 1)]
           [c 0 (+ c 1)])
          ((or (= i (+ offset length))
               (= j strlen))
           (vector-set! local 1 (+ ptr 1))
           c)
        (string-set! buffer i (string-ref str j))))))

(define null (lambda args (void)))

(define (open-input-string str)
  (unless (string? str)
    (error 'open-input-string "expected string, got '~a'.~%" str))
  (let ([port (make-custom-character-input-port
               sio/read sio/read-string null null)])
    ; Use the port local value to store the string and a pointer
    ; for the current position
    (set-port-local! port (vector str 0 (string-length str)))
    port))


;; String Output Ports

(define (sio/write port char)
  (set-port-local! port
    (cons char (port-local port))))

(define (sio/write-string port string offset length)
  (set-port-local! port
   (append (reverse (string->list (substring string offset (+ length offset))))
           (port-local port))))

(define (open-output-string)
  (let ([port
         (make-custom-character-output-port
          sio/write sio/write-string null null)])
    (set-port-local! port '())
    port))

(define (get-output-string port)
  (flush-output-port port)
  (let ([str (apply string
                    (reverse (port-local port)))])
    (set-port-local! port '())
    str))
      

Miscellaneous

Pretty-Printing

SISC includes a pretty-printer, a function that behaves like write, but introduces whitespace in order to make the output of data more readable to humans.

procedure: (pretty-print value [output-port]) => unspecified

Pretty-prints the specified value, either to the specified output-port, or to the console if no output-port is specified.

Source Loading

The load procedure accepts URLs as well as ordinary file names. See the section called “URLs” for details on what kinds of URLs are supported.

The file name passed to load is resolved relative to the current-url parameter. During the execution of load, current-url is set to the loaded file, so that any invocations of load from the loaded file resolve the given file name relative to the file currently being loaded. For example, lets assume we have a web site that serves the following files:

 ;;;contents of http://foo.com/bar/bar1.scm ;;;
(load "bar2.scm")
;;;contents of http://foo.com/bar/bar2.scm ;;;
(load "/baz/baz1.scm")
;;;contents of http://foo.com/baz/baz1.scm ;;;
(load "../baz/baz2.scm")
;;;contents of http://foo.com/baz/baz2.scm ;;;
(display "Hello")
        

Invoking

(load "http://foo.com/bar/bar1.scm")
        

results in each file being loaded; with the last file in the chain, baz2.scm, displaying Hello.

The load function supports many types of files which contain executable code. load will attempt to determine (primarily by the file's extension) which type of file is being loaded to load that file in the correct manner. The file types currently supported are those described in the section called “Running Scheme Programs”.

When a pure source file is loaded, each s-expression is evaluated in sequence, exactly as if entered into the REPL one s-expression at a time.

Location Tracking

SISC allows the location of input, i.e. the file name, line number, and column number, to be tracked when reading from an input port.

procedure: (open-source-input-file url) => input-port

This procedure behaves the same as open-input-file, except that it also tracks the location of the input.

procedure: (input-port-location input-port) => list

Returns the current location information associated with input-port. The return value is an association list containing the following keys: source-file, line-number, column-number. If no location information is available, #f is returned.

Locating Resources

SISC provides a mechanism for locating and subsequently loading named resources, such as Scheme source files, Scheme data files, property files. The resources are located using the mechanism described in the section called “Class Loading”. This allows Scheme programs to load resources in a portable, J2EE-compliant manner.

procedure: (find-resource string) => url

Locates the resource named by string on the Java class path. The resource location is returned as a URL suitable for SISC I/O operations. If the resource cannot be found, #f is returned.

procedure: (find-resources string) => url-list

Locates the resource named by string on the Java class path. The resource locations are returned as a list of URLs suitable for SISC I/O operations. If the resource cannot be found, an empty list is returned.

File Manipulation

Requires: (import file-manipulation)

The file-manipulation library provides access to a number of functions for reading and manipulating files and their attributes. The file-manipulation library acts on filenames in the same manner as other Scheme file related functions, e.g. it accepts file and directory names as strings, which are resolved relative to the current URL.

The following functions act on both files and directories. With the exception of file-exists? and get-parent-url, the behavior when applying these to non-existant files or directories is undefined.

procedure: (file-delete! filename) => #t/#f

Attempts to remove the given file or directory. If successful, #t is returned.

procedure: (file-exists? filename) => #t/#f

Returns true if the given file or directory exists.

procedure: (file-is-directory? filename) => #t/#f

Returns true if the given string names an existing directory.

procedure: (file-is-file? filename) => #t/#f

Returns true if the given string names an existing file.

procedure: (file-last-modified filename) => integer

Returns the number of milliseconds since the Unix epoch (Jan 1, 1970) of the date the file or directory was last modified.

procedure: (file-rename! source-filename dest-filename) => #t/#f

Renames the given source file or directory to the destination. This can be used both to rename a file or directory or to move a file/directory in the same filesystem. If successful, #t is returned.

procedure: (file-set-last-modified! filename unixtime) => #t/#f

Sets the last modified date of the given filename to the given integer (in number of milliseconds since the epoch). Returns #t if successful.

procedure: (get-parent-url filename) => string

Given any URL, returns the URL of its parent. For filenames, as an example, the parent directory is returned.

The following functions operate only on files. Their behavior when applied to directories or non-existant files is undefined.

procedure: (file-is-readable? filename) => #t/#f

Returns #t if the file can be opened for reading.

procedure: (file-is-writeable? filename) => #t/#f

Returns #t if the file can be opened for writing.

procedure: (file-length filename) => integer

Returns the length, in bytes, of the given file.

procedure: (file-set-read-only! filename) => #t/#f

Sets the given file read-only. Returns #t if successful.

Finally, the following functions are specific to directories. Their behavior on files is undefined. The behavior of directory-list is undefined on non-existant directories.

procedure: (directory-list directory) => list of strings

Retrieves the children of the given directory, as a list of strings. Each string names one child, and is a filename relative to the given directory.

procedure: (make-directory! directoryname) => #t/#f

Attempts to creates the given directory. Returns #t if successful.

procedure: (make-directories! directoryname) => #t/#f

Attempts to creates the given directory and all non-existing parent directories. Returns #t if successful.

Class Loading

Some SISC features require classes and other resources to be loaded. By default, SISC will use the current thread's class loader, or, if none is present, the system class loader. SISC maintains a list of class path extensions onto which class and resource loading falls back. This list can be inspected and extended using the following functions:

parameter: (class-path-extension) => string-list

Retrieves the current class path extension. The class path extension is a list of strings representing URLs, typically pointing to jar files or directories. It is used a fall back during class and resource loading.

parameter: (class-path-extension-append! class-path) => undefined

Appends a list of URLs to the current class path extension.

The elements of the class-path list are normalized using the current-url (see the section called “URLs”), thus permitting the usage of relative URLs.

Note that the class path extension is part of the dynamic environment,, so each thread has its own setting, initially inherited from the parent thread. See Chapter 6, Threads and Concurrency for more details on SISC's threading semantics.



[3] Handling of JAR files in URLS may be dependent on the SISC host language, as well as some uncommon protocols. FILE, HTTP and FTP should be expected to work with any host language.