In-class notes for 01/26/2018
CS 284 (MCA), Interim 2018
RESTful Java server code
Java source code
Files with name that begin with ExampleRest...
illustrate
how to use the HTTP REST API support
-
This is the sample server code, illustrating both what you would include in the main thread for a server (typically containing the
accept()
call on aServerSocket
) and what would typically go into aWorker
thread (receiving the message, parsing it, sending a reply).This and other code files are documented with Javadoc, which is good for a detailed look but could obscure the big picture. See ExampleRestServer.java_nodoc for a version with Javadoc removed.
The line
// define API handlers ExampleRestModel model = new ExampleRestModel();
is the main addition necessary in the main server code. See below for a discussion of models in this sense.(This code also starts a second thread
commandThread
that processes standard input, as opposed to socket I/O, This isn't necessary for the HTTP demo, but it provides for an orderly shut down of the server using the EXIT command entered through standard input.)The code
/***** BEGIN section to implement within a Worker thread *****/ try { ... } /***** END section to implement within a Worker thread *****/
would typically be executed by aWorker
thread.Create streams
inStream
andoutStream
for a newlyaccept
ed socket, and read a message frominStream
into abyte
arrayinBuff[]
. That message will contain an entire HTTP request corresponding to a REST API call.The lines
HttpParser parser = new HttpParser(inBuff, 0, count); int code = parser.parseRequest();
create a parser object for the HTTP request message ininBuff
. This analyzes the content of that message and extractsthe HTTP method for the request, such as
POST
,GET
,PATCH
, orDELETE
,the API URL for the message on that server, such as
/names
for the "names" server example presented earlier in the term,the parameters and headers in the message,
etc.
The call
reply = model.handle(parser);
forwards the parsed results for handling in the Model object defined above. If parsing returns an error code (anything other than 200), a reply is prepared based on that error code without further handling operations.The return value from
model.handle()
is a fully formed HTTP reply message, ready to send to the client in the calloutStream.write(reply.getBytes());
Note: The Model object
model
should be passed to eachWorker
thread, typically in an argument of theWorker
constructor, so eachWorker
can callmodel
's methods when processing an incoming HTTP request.
-
The class
ExampleRestModel
illustrates how to implement services offered by a REST API.Note: The name "Model" refers to the Model/View/Control pattern for implementing applications with user interfaces.
The data model prescribes how data is organized and managed in an application.
The
view
refers to the visible elements of a user interface, including screens, buttons, images, etc.The
control
consists of the algorithm logic for relating events in the view with the data stored in the model.
For example, when user clicks on a button in the view, the windowing system activates the control code associated with that button, which may modify and/or retrieve data in the model, perhaps leading to changes in the view (a new screen, an updated image, etc.)
ExampleRestModel.java_nodoc provides a version of ExampleRestModel.java without Javadoc, in case that helps reveal the code structure.
The constructor for
ExampleRestData
public ExampleRestModel() { addHandler("/", new CountHandler()); addHandler("/names", new NamesHandler()); }
contains calls to set up "API handlers" for implementing REST operations associated with a particular URL. These handlers implement the services associated with the URLs/
and/names
associated with this server.Note: Ordinarily, the data model uses database tables in a DBMS to store, manage, and retrieve data. In this simplified example, we will store information in local variables, rather than the safer and more robust database tables.
Consequently, you would ordinarily pass an open database connection (typically set up once in the server's main thread) as an argument to the Model constructor, so it can perform database interactions.
This design strategy locates all the database interactions in a Model class, separate from the setup and connection operations in the Server's main thread and from the communication management in the
Worker
thread class. This separation of concerns makes it easier to manage the code development and maintenance.The two
addHandler()
calls above create objects of typeCountHandler
andNamesHandler
, respectively. These types provide the custom implementation for the server's API services. In the example, these classes are implemented as inner classes withinExampleRestModel
, but they could also be implemented in separate code modules, as anonymous classes, etc. (compare to GUI component adapters discussed in an early lab).The class
CountHandler
extends the base classRestApiHandler
, which provides default implementations of four methods for handling REST API calls:doPost()
forPOST
requests (Create);doGet()
forGET
requests (Read);doPatch()
forPATCH
requests (Update); anddoDelete()
forDELETE
requests (Delete).
The default behavior in
RestApiHandler
is for all of these handler methods to generate an HTTP reply with the code501 - Not Implemented
.The subclass
CountHandler
overrides three of these methods, each of which modifies or obtains the (local) data and creates an appropriate HTTP reply message (to be sent by theWorker
thread). These custom methodsdoGet()
, etc., implement the REST API operations.Likewise, the subclass
NamesHandler
defines the/names
API operations
The other three Java source files define the classes used in the
ExampleRest....java
files. You shouldn't need
to change these unless you want to add some new capability.
HttpParser.java implements parsing of HTTP requests and generation of HTTP reply messages.
RestApiHandler.java is the base class for API Handlers such as
CountHandler
andNamesHandler
.RestApiModel.java is the base class for Models such as
ExampleRestModel
. This base class implements the methodhandle()
that looks up an appropriate API handler for a parsed HTTP request and calls the appropriatedo...()
method for that request.
Java source code
Copy the Java source files above into an appropriate subdirectory D in your working directory on the Link, and compile using
javac ExampleRestServer.java
Copy the files Test/ExampleRestServer.sh, Test/ExampleRestServer.out, and Test/tweak into a subdirectory
Test
of D. Issue the shell commandschmod +x Test/tweak Test/ExampleRestServer.sh
Also, modify Test/ExampleRestServer.sh to use one of your assigned ports.Enter the shell command
Test/ExampleRestServer.sh
to start your server. You should not see any lines of output (these are captured by the script), nor should you see another shell prompt yet.On your laptop or another machine with a
node
setup, , copy the files Test/example-client.js and Test/example-client.out to a directory set up for running javascript code. (These files don't need to be copied to a subdirectoryTest
.)Edit
example-client.js
to enter the Link machine and port you started yourExampleRestServer
on. (You can find the numerical IP address for a Link machine by enteringarp -n rns20m-n.cs.stolaf.edu
on any link machine, wherem
andn
indicate the Link machine your server is running on.To run the client, enter
node example-client.js > example-client.out.new
on a shell window on your computer. (You may need to be on the St. Olaf network for this to succeed.)Your program should return and you should soon see a new shell prompt. Then enter
diff example-client.out.new example-client.out
to see the differences in output between your client run and mine. The only difference should be the first line, indicating which server and port you used.Finally, return to your window where you started your
ExampleRestServer
on a Link machine. Enter the commandEXIT
The script will immediately print the differences between your run and my prior run. The only difference should be in the host/port and in the date headers.Note: The files ending in
.out
hold the expected output from client and server.Test/example-client.out holds the expected output from the Javascript client Test/example-client.js
Test/ExampleRestServer.out holds the expected output from the Java server ExampleRestServer.java, with all debug output turned on.
Debug output in the Java server is controlled by the state variable
DEBUG
defined in the source files ExampleRestServer.java, HttpParser.java, and RestApiModel.java. IfDEBUG
has the valuenull
, no debug output is printed; otherwise, debug output is printed prefixed by the String value ofDEBUG
. To produce Test/ExampleRestServer.out, we assignedDEBUG="DEVEL "
in all three files.
< >