| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ======================================================================== |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| |
| [[creating-custom-protocol]] |
| === Creating a Custom Protocol |
| |
| You can create custom protocols with Jetty. This page provides an example of how to do so, with Telnet as the protocol. |
| |
| To create a custom Telnet protocol, complete the following tasks: |
| |
| * Implement a `TelnetServerConnectionFactory`. |
| * Implement a `TelnetServerConnection` by extending `o.e.j.io.AbstractConnection`. |
| * Create a parser/interpreter for the bytes you receive (this is totally independent from Jetty). |
| * If needed, design an API for the application to use to process the bytes received (also independent from Jetty). |
| The API likely has a _respond back_ primitive that uses a Jetty provided `EndPoint` and `EndPoint.write(Callback, Buffer...)` to write the response bytes. |
| |
| [[server-connection-factory]] |
| ==== Implementing a TelnetServerConnectionFactory |
| |
| Begin with an `org.eclipse.jetty.server.ServerConnector`, which you can use as is. `ServerConnector` takes a `o.e.j.server.ConnectionFactory`, which creates `o.e.j.io.Connection` objects that interpret the bytes the connector receives. |
| You must implement `ConnectionFactory` with a `TelnetServerConnectionFactory`, where you return a Connection implementation (for example, `TelnetServerConnection`). |
| |
| [[telnet-server-connection]] |
| ==== Implementing the TelnetServerConnection |
| |
| For the Connection implementation you need to extend from `o.e.j.io.AbstractConnection` because it provides many facilities that you would otherwise need to re-implement from scratch. |
| |
| For each Connection instance there is associated an `o.e.j.io.EndPoint` instance. |
| Think of `EndPoint` as a specialized version of JDK’s `SocketChannel`. |
| You use the `EndPoint` to read, write, and close. |
| You don’t need to implement `EndPoint`, because Jetty provides concrete |
| classes for you to use. |
| |
| The Connection is the _passive_ side (that is, Jetty calls it when there is data to read), while the `EndPoint` is the active part (that is, applications call it to write data to the other end). |
| When there is data to read, Jetty calls `AbstractConnection.onFillable()`, which you must implement in your `TelnetServerConnection`. |
| |
| A typical implementation reads bytes from the `EndPoint` by calling `EndPoint.fill(ByteBuffer)`. |
| For examples, look at both the simpler `SPDYConnection` (in the SPDY client package, but server also uses it), and the slightly more complex `HttpConnection`. |
| |
| [[parser-interpreter]] |
| ==== Parsing the Bytes Received |
| |
| After you read the bytes, you need to parse them. |
| For the Telnet protocol there is not much to parse, but perhaps you have your own commands that you want to interpret and execute. |
| Therefore typically every connection has an associated parser instance. |
| In turn, a parser usually emits parse events that a parser listener interprets, as the following examples illustrate: |
| |
| * In HTTP, the Jetty HTTP parser parses the request line (and emits a parser event), then parses the headers (and emits a parser event for each) until it recognizes the end of the headers (and emits another parser event). |
| At that point, the _interpreter_ or parser listener (which for HTTP is `o.e.j.server.HttpChannel`) has all the information necessary to build a `HttpServletRequest` object and can call the user code (the web application, that is, servlets/filters). |
| * In SPDY, the Jetty SPDY parser parses a SPDY frame (and emits a parser event), and the parser listener (an instance of o.e.j.spdy.StandardSession) interprets the parser events and calls user code (application-provided listeners). |
| |
| With `ConnectionFactory`, Connection, parser, and parser listeners in place, you have configured the read side. |
| |
| [[api-byte-processor]] |
| ==== Designing an API to Process Bytes |
| |
| At this point, server applications typically write data back to the client. |
| |
| The Servlet API (for HTTP) or application-provided listeners (for SPDY) expose an interface to web applications so that they can write data back to the client. |
| The implementation of those interfaces must link back to the `EndPoint` instance associated with the Connection instance so that it can write data via `EndPoint.write(Callback, ByteBuffer...)`. |
| This is an asynchronous call, and it notifies the callback when all the buffers have been fully written. |
| |
| For example, in the Servlet API, applications use a `ServletOutputStream` to write the response content. |
| `ServletOutputStream` is an abstract class that Jetty implements, enabling Jetty to handle the writes from the web application; the writes eventually end up in an `EndPoint.write(...)` call. |
| |
| [[api-tips]] |
| ===== Tips for Designing an API |
| |
| If you want to write a completely asynchronous implementation, your API to write data to the client must have a callback/promise concept: “Call me back when you are done, and (possibly) give me the result of the computation." |
| |
| SPDY’s Stream class is a typical example. |
| Notice how the methods there exist in two versions, a synchronous (blocking) one, and an asynchronous one that takes as last parameter a Callback (if no result is needed), or a Promise (if a result is needed). |
| It is trivial to write the synchronous version in terms of the asynchronous version. |
| |
| You can use `EndPoint.write(Callback, ByteBuffer...)` in a blocking way as follows: |
| |
| [source, java, subs="{sub-order}"] |
| ---- |
| FutureCallback callback = new FutureCallback(); |
| endPoint.write(callback, buffers); |
| callback.get(); |
| ---- |
| |
| With the snippet above your API can be synchronous or asynchronous (your choice), but implemented synchronously. |