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 aWorkerthread (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
commandThreadthat 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 aWorkerthread.Create streams
inStreamandoutStreamfor a newlyaccepted socket, and read a message frominStreaminto abytearrayinBuff[]. 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
/namesfor 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
modelshould be passed to eachWorkerthread, typically in an argument of theWorkerconstructor, so eachWorkercan callmodel's methods when processing an incoming HTTP request.
-
The class
ExampleRestModelillustrates 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
viewrefers to the visible elements of a user interface, including screens, buttons, images, etc.The
controlconsists 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
ExampleRestDatapublic 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/namesassociated 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
Workerthread class. This separation of concerns makes it easier to manage the code development and maintenance.The two
addHandler()calls above create objects of typeCountHandlerandNamesHandler, 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
CountHandlerextends the base classRestApiHandler, which provides default implementations of four methods for handling REST API calls:doPost()forPOSTrequests (Create);doGet()forGETrequests (Read);doPatch()forPATCHrequests (Update); anddoDelete()forDELETErequests (Delete).
The default behavior in
RestApiHandleris for all of these handler methods to generate an HTTP reply with the code501 - Not Implemented.The subclass
CountHandleroverrides three of these methods, each of which modifies or obtains the (local) data and creates an appropriate HTTP reply message (to be sent by theWorkerthread). These custom methodsdoGet(), etc., implement the REST API operations.Likewise, the subclass
NamesHandlerdefines the/namesAPI 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
CountHandlerandNamesHandler.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
Testof 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
nodesetup, , 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.jsto enter the Link machine and port you started yourExampleRestServeron. (You can find the numerical IP address for a Link machine by enteringarp -n rns20m-n.cs.stolaf.edu
on any link machine, wheremandnindicate 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
ExampleRestServeron 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
.outhold 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
DEBUGdefined in the source files ExampleRestServer.java, HttpParser.java, and RestApiModel.java. IfDEBUGhas 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.
< >