Project B
- Released
-
30 September 2021 - Submission
-
Deadline
15 October 2021 (for Feedback) 21 October 2021 @ 5 PM EDT Where Web Submit Name B Files B.war - Version
Table of contents
Executive Summary
In this project, you will create a number of multithreaded WEB services which utilize HTTP over TCP. These services will build on the TCP services of Project A and expose a number of new concepts, such as industrial-strength app servers, session management, analytics, and federataed and open authentication. You will later use these services to build a shopping-cart web application.
The Environment
We will continue to use the remoteLab, but unlike Project A for which any editor or IDE will do, we will now leverage Eclipse with Java EE features. To set this up, do this:
- Launch Eclipse.
- The Servers view should be visible (normally in the bottom pane with the console). If not, enable it via
Show View
in theWindow
menu. - Right-click anywhere inside the empty
Servers
view and chooseNew > Server
. - Add
Tomcat v9.0 Server at localhost
by pointing to its directory at:/eecs/home/<user>/4413/pkg/tomcat
. - Create a new
Dynamic Web Project
namedB
and accept all defaults. - Right-click the project and select
New > Servlet
and name itService
(put it in theservices
package) and accept all defaults. -
One time only (since it gets cached), associate the
Servlet
Javadocs with Eclipse as follows:- Right-click the project and select
Build Path > Configure Build Path...
. -
Under the
Libraries
tab, expand open and select:Apache Tomcat v9.0 > servlet-api.jar > Source Attachment
-
Select the
External location
option and set the location to/eecs/home/<user>/4413/pkg/tomcat/webapps/docs/servletapi/apache-tomcat-9.0.8-src.zip
- Click
OK
, thenApply
andApply and Close
.
- Right-click the project and select
- Right-click
Service
, selectRun > Run on Server
, selectTomcat v9.0 Server at localhost
and checkAlways use this server when running this project
. ClickFinish
. You can now visit its URL from a browser. Use this servlet as a testbed to explore features and try out ideas.
Tomcat Tips
Once Tomcat is started, there is no need to click
Run
. It should automatically publish (re-synchronise) the servlets. Occassionally, you will get the error:Several ports (5413, 4413, 3144, 6413) required by Tomcat v9.0 Server at localhost are already in use. The server may already be running in another process, or a system process may be using the port. To start this server you will need to stop the other process or change the port number(s).
You may see this despite Eclipse reporting that the Tomcat server is stopped and there is nothing shown in the console. To manually stop Tomcat, open your terminal and run:
tomcatOff
Then retry starting the server.
To display Eclipse’s Tomcat configuration panel, open the
Servers
view and double-click on theTomcat v9.0 Server at localhost
entry. There you will the Tomcat configurations for automatic publishing, ports, timeouts, etc.
Adding External Libraries to Tomcat
- Right-click your Project in the
Project Explorer
. -
Navigate to:
Run As > Run Configurations
-
On the left, select:
Apache Tomcat > Tomcat v9.0 Server at localhost
- Click the
Classpath
tab and selectUser Entries
within the list hierarchy. - Click
Add External JARs
. -
In the popup window, navigate to your Home directory and then the
4413
folder. Go to:/eecs/home/<user>/4413/lib
- Select all of the JARs files and click
Open
. -
Now you will see that the JARs listed under ``User Entries`.
Note:
derbyclient.jar
seems to be missing dependencies. When you run the Tomcat server, this will result in a lot ofFileNotFoundException
exceptions being thrown in your console. You can ignore these or:- Click
derbyclient.jar
and clickRemove
. - Click
Add External JARs
and navigate to:/eecs/home/<user>/4413/pkg/derby/lib
- Select the copy of
derbyclient.jar
there and clickOpen
.
- Click
- Click
Apply
and thenRun
to start the server.
You may need re-add the external libraries to your new Project. To do so, follow the same steps from Project A.
The Services
This Project asks that you develop and test five HTTP microservices with the following functional specifications:
FAuth — A Gateway Web Service
This Federated Authentication service receives two URL-encoded parameters username
and password
and returns OK
or FAILURE
in a text/plain payload based on the authentication of the Auth
service of Project A. In other words, FAuth
does not do any authentication itself. Instead, it simply delegate to Auth
by turning its URL parameters to a TCP request line and by turning the TCP response line to an HTTP response.
GeoWeb — A Stateful Web Service
This service is the web counterpart of the stateful GEO2 service of Project A. It receives two URL-encoded parameters: lat
and lng
, containing the GPS coordinates of a place on Earth. If this is the first time this client has made such a request, then the return would be the text/plain
payload “RECEIVED”. If not, then the return would be the text/plain
payload:
The distance from (lat1, lng1) to (lat2, lng2) is: XXX km
where XXX
is the geodesic distance between the previous place (lat1
and lng1
, sent in the last request) and the current one (lat2
and lng2
, sent in this request). This can continue (to a 3rd, 4th, … places) as long as the requests are made in the same session. All computations must be made by delegating to the Geo
service, not Geo2
, of Project A; i.e. this web service does not do any math, and it uses Tomcat’s session capabilities (rather than a 3rd parameter) to persist data.
Loc — An API Web Service
Given a street address (partial or complete) anywhere on Earth as a GET parameter, named location
, this service returns in the HTTP payload a JSON object representing the address’s specs; most importantly, its latitude and longitude. Use the MapQuest’s API (http://www.mapquestapi.com/geocoding/v1/address?
) and supply the address and your API key to perform this lookup and capture the latLng
element in the returning JSON (see below). If MapQuest returns multiple results, return the first location’s latitude and longitude.
For example, the given request:
GET /loc?location=4700+Keele+Street+Toronto
Your service should response with:
{ "lat": 43.775124, "lng": -79.494075 }
If no location is found, your service should respond with:
{ "lat": null, "lng": null }
Getting a MapQuest API Key
You will need a MapQuest Developer account. To create a new account, register to here. Once you have completed the form and click
Sign Me Up
, you will be taken to your Profile.Click Manage Keys on the left navigation to go to your API keys. MapQuest automatically creates you an API key when you first create your account. You can find it by expanding
My Application
where you will find yourConsumer Key
. This is your API key, you will use in your GET request.
Developer Tips
On the API documentation page, you will find an example GET request on the left:
GET http://www.mapquestapi.com/geocoding/v1/address?key=KEY&location=Washington,DC
Replace
KEY
with your API key and try it out in your web browser. You should get the same results as the Example Response. You can change response format by providing the optional query parameteroutFormat
in your request. The supported formats are: JSON, XML and CSV. JSON is the default format.
Whitespaces in the Location Query
Your location query parameter should permit whitespaces to be contained within it. However, URLs cannot contain whitespaces. To get around this, we encode our URLs. See here and here.
It may be one potential reason why your API request to MapQuest fails. To fix this, we could implement the encoding scheme ourselves, however, there are existing implementations of encoders we can use. One exists in the
java.net.URLEncoder
class.String urlString = URLEncoder.encode(urlString, "UTF-8");
This method translates the given string into the
application/x-www-form-urlencoded
format using a specific encoding scheme. This method uses the supplied encoding scheme to obtain the bytes for unsafe characters.
Where’s the latLng object?
The exact path to
latLng
element in the returning JSON is…Unfortunately, there is no standardized way to specify it without a verbose description, but there are draft proposals, such as JSONPath. In the JSONPath draft specification, the exact path to the
latLng
element is:$.results[0].locations[0].latLng
where
$
is the root of the JSON object. The rest, you read like how you would when accessing a nested Java object. Remember, you still need to use Gson’sgetAsJsonArray
andgetAsJsonObject
methods to walk the returning JSON object and theJsonArray#get(index)
andJsonObject#get(key)
methods to accessing the nested elements.
Pro Tip
You can also use an API explorer tool like hoppscotch.io to explore the MapQuest API. For security reasons, by default, Hoppscotch only works with HTTPS requests. To make HTTP requests, you will need to install the Hoppscotch Firefox plugin. Once installed, you should be able to test the MapQuest API and your own web services.
Drone — A Composite Web Service
Given two street addresses, this service returns an estimate of the drone delivery time in minutes for a shipment from one location to the other. Assume an average drone cruising speed of 150km/h. This service should receive the two addresses in the query parameters: source
and destination
. It should return the text/plain
response:
The estimated delivery time is: XXX minutes.
where XXX
is the estimated delivery time.
Hint
The
GeoWeb
,Drone
andLoc
services should all share the same model objects. There should only be a single implementation of code retrieving the MapQuest API and extracting the latitude and longitude values from the response JSON objects and a single implementation for connecting to theGeo
service from Project A to get the geodesic distance. There should be no code duplication.
OAuth — A Redirecting Web Service
This Open Authentication service allows users to authenticate using a third-party website; i.e. it does not receive (and hence cannot “sniff” or “leak”) any credentials; i.e. it facilitates single sign-on. To authenticate, this service redirects the client (via response.sendRedirect
) to this URL:
https://www.eecs.yorku.ca/~roumani/servers/auth/oauth.cgi
This website expects a URL parameter named back
containing the URL of the OAuth servlet that originated the request. The URL prompts users for their Passport-York Credentials, and if the authentication succeeded, it redirects them back (using the back
parameter) and sends the usernamee and full name as parameters named user
and name
. Otherwise, it issues a 401 Unauthorized and does not redirect back. If the oauth.cgi
successfully redirects back to your servlet, print to your web browser the plain text message:
Hello, <name>. You are logged in as <user>.
where <name>
and <user>
are the query parameters returned to your servlet from oauth.cgi
.
Service Implementation
Use the design patterns, methodologies, and hints demonstrated in lecture in order to speed up development and learn best-practice approaches.
- Develop all 5 services in one project, but each in its own servlet.
- The servlet name and its URL mapping should be the same as the service name.
- Separate the model (business rules) from the controller (http communication). You can have one model class per service or one model for all three services. It is customary to put all servlet classes in one package named
service
and the model class(es) in one package namedmodel
. - Configurations, such as: the IP addresses and port numbers of the
Geo
andAuth
services, your MapQuest API key, the MapQuest API URL, and the OAuth URL should be stored outside of your Java program in a configuration file, like aweb.xml
file stored in theWebContent/WEB-INF
folder of your Project. (See below for details.)
About the web.xml Deployment Descriptors File
The
web.xml
file is a standard configuration file used to specify details and configuration regarding your server and its servlets. It allows your webapp to be portable, i.e. moved to a different server with different configurations or a different file path and still work with only minor changes to the configuration file. The webapp shouldn’t need to be re-compiled or edited to run in a different environment.What is the web.xml file?
Java web applications use a deployment descriptor file to determine how URLs map to servlets, which URLs require authentication, and other information. This file is named
web.xml
, and resides in the app’sWAR
under theWEB-INF/
directory.web.xml
is part of the servlet standard for web applications.A web application’s deployment descriptor describes the classes, resources and configuration of the application and how the web server uses them to serve web requests. When the web server receives a request for the application, it uses the deployment descriptor to map the URL of the request to the code that ought to handle the request.
Here is an example
web.xml
file:<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0" > <display-name>Project B</display-name> <context-param> <param-name>MapQuestAPIKey</param-name> <param-value>12345abcdefghijklmnop</param-value> </context-param> ... <servlet> <servlet-name>FAuth</servlet-name> <servlet-class>services.FAuth</servlet-class> <init-param> <param-name>serviceAddress</param-name> <param-value>130.63.168.150</param-value> </init-param> <init-param> <param-name>servicePort</param-name> <param-value>40215</param-value> </init-param> </servlet> <servlet> <servlet-name>GeoWeb</servlet-name> <servlet-class>services.GeoWeb</servlet-class> <init-param> <param-name>serviceAddress</param-name> <param-value>130.63.168.99</param-value> </init-param> <init-param> <param-name>servicePort</param-name> <param-value>35155</param-value> </init-param> </servlet> ... </web-app>
In your Java servlet, you can access these configurations, like this:
@WebServlet( name = "FAuth", // It's critical that you specify the servlet name, urlPattern = {"/FAuth"} // and URL pattern here. You can specify URL pattern // in the web.xml as <server-mapping>, but you must // specify the servlet name here. ) public class FAuth extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... getServletContext().getInitParameter("MapQuestAPIKey"); // "12345abcdefghijklmnop" getInitParameter("serviceAddress"); // "130.63.168.150" getInitParameter("servicePort"); // "40215" ... } } @WebServlet( name = "GeoWeb", // It's critical that you specify the servlet name, urlPattern = {"/GeoWeb"} // and URL pattern here. You can specify URL pattern // in the web.xml as <server-mapping>, but you must // specify the servlet name here. ) public class GeoWeb extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... getServletContext().getInitParameter("MapQuestAPIKey"); // "12345abcdefghijklmnop" getInitParameter("serviceAddress"); // "130.63.168.99" getInitParameter("servicePort"); // "35155" ... } }
For more information about the
web.xml
Deployment Descriptors file, see here and here.Note: Since both the
GeoWeb
andDrone
services use theGeo
service. It may make more sense to declare the IP and port number forGeo
as context parameters instead of declaring them twice as initial parameters for both theGeoWeb
and theDrone
servlets.... <context-param> <param-name>GeoServiceIP</param-name> <param-value>130.63.168.99</param-value> </context-param> <context-param> <param-name>GeoServicePort</param-name> <param-value>35155</param-value> </context-param> ...
Alternatively, you could use the Tomcat-specific
context.xml
configuration file (placed in yourWebContent/META-INF
folder) to specify the Context as well as database connections.<?xml version="1.0" encoding="UTF-8" ?> <Context privileged="true" reloadable="true"> <Parameter name="MapQuestAPIKey" value="12345abcdefghijklmnop" override="true"/> <!-- Equivalent to: <context-param> <param-name>MapQuestAPIKey</param-name> <param-value>12345abcdefghijklmnop</param-value> </context-param> in the web.xml. --> <Resource name="jdbc/EECS" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" type="javax.sql.DataSource" driverClassName="org.apache.derby.jdbc.ClientDriver" url="jdbc:derby://localhost:64413/EECS" /> <ResourceLink global="jdbc/EECS" name="jdbc/EECS" type="javax.sql.DataSource" /> </Context>
For more information about the
context.xml
, see here.
Testing & Deployment
Test your services through a browser using http://localhost:4413/<project>/<service>/?<qs>
, where project
is the name of your web project, service
is the name of your service (or its URL mapping) and qs
is the query string. Once all is well, switch to the localhost address and test by deploying your services on one workstation and accessing it (via a browser) from a different workstation in the lab.
Submitting Your Work
Note that this process is different from that of Project A.
Follow these steps to persist / backup your work:
- Right-click your Project (in the
Project Explorer
) and selectExport
. - Select
WAR File
. - Provide a destination for the file (e.g.
B.war
on the Desktop). - Check the box to
Export the source files
(extremely important) -
Now upload the created
WAR
file to the course cloud so you can use it during tests. (You can also upload it to your Google Drive, DropBox, S3, or some other cloud service). Via the submit command:$ submit 4413 B B.war
If you later need to restore this backup into a fresh workspace, do this:
- Launch Eclipse in a new workspace.
- Right-click anywhere in the
Project Explorer
and selectImport
. - Select
WAR file
. - Point to your
WAR
file and clickFinish
.
Try the above procedure end-to-end (by using two machines; by switching to a different workspace on the same machine; or by deleting your workspace after the WAR
file has been created). Make sure you are comfortable backing up and restoring your course project. Do not wait until the day of the test to learn how to do this. Do not delay the backup until the work is done! Do it often (at least after every release). If you are familiar with Github
then do use it but you still need to upload to the course cloud and to practice the above backup/restore procedure. Finally, note that the above WAR
file approach is the best way to copy and/or rename your webapp.