首页 > > 详细

CIS 455/555: Internet and Web Systems Homework 1: Web server

 CIS 455/555: Internet and Web Systems

Fall 2020
Homework 1: Web server and Microservice Framework
Milestone 1 due September 30, 2020, at 10pm ET
Milestone 2 due October 12, 2020, at 10pm ET
1. Background
This assignment focuses on developing an application server, i.e., a Web (HTTP) server that runs Java￾based services. You’ll be doing this in two stages.
In the first stage, you will implement a simple HTTP server for static content (i.e., files like images, style 
sheets, and HTML pages). This web server will allow you to get a nice, limited-scale introduction to 
building a server system as it requires careful attention to concurrency issues and well-designed 
programming abstractions to support extensibility.
In the second stage, you will expand your webserver to handle Web service calls. Web service calls are the 
underpinnings of many aspects of the cloud and are used to invoke operations remotely. One method for 
creating services in Java is through servlets, with which you might be familiar if you’ve taken CIS 450/550. 
Increasingly, however, there has been a movement towards lighter-weight RESTful services that expose 
APIs over HTTP’s GET, POST, DELETE, PUT, and other operations. Such services are typically written 
by attaching handlers (functions called by the Web server) to various routes (URL paths and patterns). 
There are many frameworks that follow this paper, e.g., Node.js for JavaScript, Django for Python. In Java, 
perhaps the most popular framework for such operations is Spark Java (not to be confused with Apache 
Spark). We will implement a microservices framework that emulates the Spark API.
Eventually, your Homework 1 server and framework will be used in your Google-style final projects to (1) 
serve HTML pages, e.g., for your search engine; (2) support actions in response to HTML forms, e.g., to 
trigger the actual search; (3) support REST Web service calls, e.g., to build a distributed web crawler.
1.1. A Sample Program in the Spark Framework
This homework assignment consists of two milestones. Technically, you can build a one-off solution for 
Milestone 1, then throw much of it away in order to do Milestone 2. That’s not what we recommend --
instead we want you to understand where you are going with Milestone 2 before embarking on Milestone 
1. If you implement Milestone 1 using the same raw framework as Milestone 2 (not necessarily 
implementing every bell and whistle initially), you’ll be much better positioned for the second milestone.
Thus, we begin by explaining the programming abstraction we are providing (cloning from open source) 
for this project. Let’s look at a sample Spark Framework program, from their “Getting Started” 
documentation.
import static spark.Spark.*;
public class HelloWorld {
public static void main(String[] args) {
get("/hello", (req, res) -> "Hello World");
} }
Upon a browser request (to localhost:45555/hello), it returns “Hello World.” Super simple, right?
Before diving into how it works, it is worth noting two aspects of Java 8 that you may not have seen.
1. import static spark.Spark.* finds the spark.Spark class (in a JAR file) and imports all static 
functions in the Spark class to the global scope. Thus, public static void Spark.get(...)
is callable as get().
2. (req, res) -> “Hello World” is a lambda function that may be familiar to you if you have used 
functional languages. It takes a pair of input parameters, req and res, and returns a string (“Hello 
World”). The types of req, res, and the function return value were defined by the get() function 
itself: namely, they are of type Route which takes a Request and a Response, returns a String, 
and optionally throws a HaltException if the request results in an error.
Recall that HTTP defines a mechanism for the Web server (or a client) to make a request, and that the server 
must do some computation and send back a response.
The call to get is registering a route handler for HTTP GET requests (to /hello). (Other kinds of HTTP 
requests, such as POST, DELETE, HEAD, etc. are also mapped to route handlers using the same basic 
style.) This simple route does not have any patterns or variables, but more complex routes can match on 
patterns in the URL, define parameters in the path, or parse an HTTP query string. The route handler is the 
trivial “Hello World” lambda function. In Spark, the handler writes results to a String return result, but 
also has the ability to modify aspects of the HTTP response by modifying the Response object. If 
something goes wrong, the handler can throw a HaltException that returns an HTTP error code.
A more interesting sample program is shown below:
// matches "GET /hello/foo" and "GET /hello/bar"
// request.params(":name") is 'foo' or 'bar'
get("/hello/:name", (request, response) -> {
String cookie = request.cookies("cookie");
response.type("text/plain");
return "Hello: " + request.params(":name") + cookie;
});
Later in the semester, we’ll see how REST calls are mapped to these same sorts of routes.
1.2. The Basic CIS 455/555 HTTP Server Framework
Our code framework is very heavily based on the Spark Framework, in order to ensure it is realistic. As 
such, your code may be tested against a variety of JUnit tests, and some aspects of it need to conform to 
our API.
As the saying goes, a picture is worth a thousand words. So we’ll start with an illustration of the standard 
“flow” among the classes we have given you, which we expect to occur as Web requests are made.
The WebServer contains your main function, any server configurations, and takes command line 
arguments. With the help of the WebServiceFactory, it can create and configure a WebService, which 
does most of the work processing and handling requests. Internally, the WebService contains an 
HttpListener that listens for incoming requests on a ServerSocket, a thread-safe HttpTaskQueue and a 
series of HttpWorkers that consume tasks from the queue. Each worker, as it receives an HttpTask, uses 
the HttpIoHandler to parse the data on the socket, create a Request, call some type of RequestHandler
to handle the request, and creates a Response that is then sent via HttpIoHandler to the client.
For the first milestone, the handler will take a limited subset of possible information from the Request, 
such as the URL path and either return a file, show some control information, or shutdown the server. 
Subsequently, in Milestone 2 you’ll generalize the handler to invoke user-defined routes.
2. Developing and running your code
Now you have some understanding of how things are supposed to work. Before you start writing an 
implementation, we strongly recommend that you do the following:
1. Carefully read the entire assignment (both milestones) from front to back and make a list of the 
features you need to implement.
2. Read the debugging and testing tips below, for hints on how to approach testing and debugging.
3. Check out the project ProducerConsumerDemo from Bitbucket (https://bitbucket.org/upenn￾cis555/555-producer-consumer.git; refer to HW0 instructions for cloning from Git and importing 
into Eclipse) and see if you can understand how it (1) uses synchronization and (2) uses logging.
Where might these techniques be useful in your implementation?
4. Think about how the key features will work. For instance, before you start with MS2, go through 
the steps the server will need to perform to handle a request. If you still have questions, have a 
look at some of the extra material on the assignments page, or ask one of us during office hours.
5. Spend at least some time thinking about the design of your solution. What classes, beyond the ones 
we have provided, will you need? How many threads will there be? What will their interfaces look 
like? Which data structures need synchronization? And so on.
6. Regularly check your changes into your git repository. This will give you many useful features, 
including a recent backup and the ability to roll back any changes that have mysteriously broken 
your code.
Do NOT look at or use any third-party code other than the standard Java libraries (exceptions noted 
in the assignment) and any code we provide. Yes, there are higher-level libraries that do some of the 
core functionality we want -- but the goal is to learn how these work!
2.1 Getting the Homework Source Code
We recommend that you continue using Eclipse and Bitbucket as with HW0. As with HW0, you should 
first fork the framework code for HW1 (from https://bitbucket.org/upenn-cis555/555-hw1) to your 
own private repository; then clone and import it. Recall that there is a bit of work to set the Maven run 
options to clean, install, and execute the code.
One difference in the maven “pom.xml” file is a set of arguments in the “exec-maven-plugin” section
seen below. These configuration options specify the main class as well as two arguments that will be passed 
into the main method when using the goal “exec:java”. These arguments will be explained in Section 3.
edu.upenn.cis.cis455.WebServer
45555
/vagrant/555-hw1/www
To ensure proper grading, your submission must meet the requirements specified in Sections 3.3 and 4 
below - in particular, it must build and run correctly in Eclipse and have an mvn script that correctly builds 
and runs the code. During submission, the build server will attempt to “vet” your code and let you know if 
there are any issues, but we cannot promise that this pre-screening will be 100% effective.
2.1. Logging, testing, and mock objects
In this homework, we make use of three standard techniques and tools that are extremely valuable when 
trying to develop high-quality code (and save both sleep and sanity). Note that tests should help you flesh 
out, develop, and validate your code and you should be writing tests even as you write your code. (There 
are some who believe you write the test cases first and then write the code!) You should make use of:
1. Logging infrastructure. You’ve probably used “System.out.println” at various points to see 
what’s going on in your program. Logging tools, like Apache Log4J, are a generalization of this 
idea and allow much finer-grained control of what messages you see and where you see them.
2. Modular interfaces and unit tests. You have probably used JUnit tests before to validate 
functionality in your code. For something like a Web server, unit-testing sub-functions (e.g., the 
HTTP request parser, sleep and wake-up on the request queue, setting the response variables 
correctly, setting cookies) is extremely helpful in validating the overall functionality. Maven helps 
automate this process.
3. Mock objects. Sometimes you’ll need to simulate different parts of the code with stubs or fake 
(“mock”) objects. For instance, to test HTTP parsing from a socket, you might want to create a 
“fake” socket that lets you closely manage what is sent and received.
2.1.1. Logging in Log4J
Let’s first talk about the logging system. Apache Log4J (and other implementations of logging 
infrastructure) allow your program to log progress, in several ways that are more powerful than 
“System.out.println”:
1. The log can go to a file instead of, or in addition to, the console. In real server applications, you 
would always write your data to a log file.
2. The logging infrastructure records what code wrote the log message, in addition to the message 
itself. That can be incredibly helpful in debugging!
3. The logging infrastructure lets you specify different levels of messages: low-level debug messages 
(DEBUG), informational messages (INFO), warnings (WARN), and full errors (ERROR). You 
can set the level of messages you want to see, e.g., to disable debug messages when you think the 
program is mostly running OK.
4. The logging infrastructure lets you turn on and off the sources of messages: you may only want to 
look at logging messages from a certain class or package.
We are using Apache Log4J v2 in this course. This version of Log4J will optionally look for a log4j.yaml
describing where you want to send the log messages, what you want to capture, etc. You can find details 
on this via Google. For simplicity we are actually just setting the Log4J configuration in the main program, 
e.g, edu.upenn.cis.cis455.WebServer with a call to Configurator.setLevel that records DEBUG 
(and above) messages for everything in the edu.upenn.cis.cis455 package. By default, the output will 
go to the console.
For each class that uses the logger, you need to (1) create a static logger object for the class, (2) write to the 
logger. If you look at edu.upenn.cis.cis455.m1.handling.HttpParsing you’ll see at the top how we 
create the logger, ultimately parameterizing it with the class. Be sure to pass in the correct class to the 
getLogger() function, as this is used to help you track and filter where log messages are originating. 
Finally, you can call logger.info(), logger.debug(), logger.error(), etc. in your code to write to the 
log.
2.1.2. Unit tests and mock objects
As mentioned previously, you should think carefully about the components of your server and how to 
expose the “right” interfaces. Hopefully the set of default classes and the figure early in this document 
will help a bit. If you do your design well (or iterate until you get it right), you’ll likely have some logical 
ways of testing the functionality in a self-contained way. It’s time to develop a set of unit tests in 
JUnit. If you put them anywhere under the src/test/java directory in your project, Maven will 
automatically run the tests when you build the program -- thus making sure you didn’t inadvertently 
introduce bugs.
We’ve given you one sample JUnit test, edu.upenn.cis.cis455.m1.server.TestSendException, 
which is designed to test that the HttpIoHandler.sendException function correctly sends a 404 HTTP 
error when called. You might want to do something similar to test that you can parse requests (and send 
errors when the parsing fails), to test that a correct response is sent, etc.
If you look at our sample code in the provided TestHelper, you’ll see that we create a “mock object” for 
the request Socket, using a library called Mockito, and making use of ByteArray streams to emulate the 
input and output to the socket. You can see the code here:
Socket s = mock(Socket.class);
byte[] arr = socketContent.getBytes();
final ByteArrayInputStream bis = new ByteArrayInputStream(arr);
when(s.getInputStream()).thenReturn(bis);
when(s.getOutputStream()).thenReturn(output);
when(s.getLocalAddress()).thenReturn(InetAddress.getLocalHost());
when(s.getRemoteSocketAddress()).thenReturn(
InetSocketAddress.createUnresolved("host", 8080));
The s object partly emulates a Socket, but is in fact not a real socket. We create a “fake” input stream that 
is returned on a call to s.getInputStream() -- instead this returns the ByteArrayInputStream bis. 
Similarly, s.getOutputStream() returns the ByteOutputStream output. Similarly, we create a default 
InetSocketAddress for a call to s.getRemoteSocketAddress(). When we pass this object s to our code￾to-be-tested, it can’t tell this isn’t a “real” socket so it will read/write as appropriate. When we test 
sendException() we expect it to not actually read from the input stream, but to write to the output stream. 
Now we can read the output stream from the byte array “underlying” the ByteArrayOutputStream, convert 
it to a string, and test what is in the string.
You should be able to generalize your own unit tests from this sample. Note that we’ve shown how to pass 
in an input to the mock socket here -- which isn’t useful for testing the sendException() function but 
might be useful in your own code.
2.2. “Integration testing” your server
If you did a good job with your unit tests, a lot of the basic functionality will work in the server. However, 
you ultimately need to “integration testing” as a whole server. Please take a look at the debugging/testing 
tips we have mentioned below, just as a reminder of how to chase down the inevitable bugs.
The expected “main program” for your Web server should be WebServer. To test your whole server, you 
have a variety of options. Note that, by default, utilities on your host OS will only be able to test your 
server if it is running at port 45555, but utilities on your guest OS can test on any port. Regardless, you 
should ensure that your server works correctly on any port by modifying the “pom.xml” file.
● You can use the Developer Tools in Chrome to inspect the HTTP headers. On your host machine, 
use Chrome to visit your webserver at http://localhost:45555. Open the main Chrome menu, choose 
"More Tools", and click on "Developer Tools". This should pop up a new window. Click on the 
Network tab, which will list all the HTTP requests processed by Chrome (click on a request for 
extra details). 
● If you want to check whether you are using the correct headers, you may find the site 
http://websniffer.cc/ useful. 
● From the Terminal on either your host OS or VM, you can use the telnet command to directly 
interact with the server. Run:
sudo apt-get update
sudo apt-get install telnet
telnet localhost
Then type in a GET request and hit Enter twice; you should see the server's response.
More advanced tools, as you have a “mature” server and want to see how well it does, can be run from the 
Terminal:
● You may also want to consider using the curl command-line utility to do some automated testing 
of your server. curl makes it easy to test HTTP/1.1 compliance by sending HTTP requests that 
are purposefully invalid - e.g., sending an HTTP/1.1 request without a Host header. 'man curl' 
lists a great many flags.
● To stress-test your server, you can use Apachebench: o Run sudo apt-get update and then sudo apt-get install apache2-utils. o Apachebench (ab) can be configured to make many requests concurrently, which will help 
you find concurrency problems, deadlocks, etc.
We suggest that you use multiple options for testing; if you only use Firefox, for instance, there is a risk 
that you hard-code assumptions about Firefox, so your solution won't work with curl or ab. You may also 
want to compare your server's behavior (NOT performance!) with that of a known-good server, e.g., the 
CIS web server. Please do test your solution carefully! Also, please do NOT run Apachebench across 
major Web sites, as you will likely make them angry for doing what looks like a DoS attack on them!
3. Milestone 1: Multithreaded HTTP Server
For the first milestone, your task is relatively simple. You will develop a Web server that can be invoked 
from the command line, taking the following parameters, in this order:
1. Port to listen for connections on. Port 80 is the default port in HTTP, but it is often blocked by 
firewalls, so your server should be able to run on any other port (e.g., 45555). 45555 is currently 
the only one forwarded between your host and guest OSes. You can modify the Vagrantfile to 
change this.
2. Root directory of the static web pages. For example, if this is set to the directory /var/www, a 
request for /mydir/index.html will return the file /var/www/mydir/index.html.
(do not hard-code any part of the path in your code - your server needs to work on a different 
machine, which may have completely different directories!) By default, we have included a 
subdirectory called www, but you should not assume this is the only possible Web “home” directory.
So, for instance, if you go into your /vagrant/555-hw1 directory and run mvn exec:java -Dexec.args 
=”45555 /home/vagrant/myweb” this should run your server on port 45555. By default, if no 
parameters are given, you should assume that your server uses port 45555, and that the root directory 
for static Web pages is “./www”.
For this milestone, your program will accept incoming GET and HEAD requests from a Web browser, and it
will make use of a thread pool (as discussed in class) to invoke a worker thread to process each request. 
When an HTTP request is received by the worker, simplified Request and Response objects should be 
created (see ...m1.interfaces for the foundations), and a simple FileRequestHandler should determine 
which file was requested (relative to the root directory specified above) and return the file. If a directory 
was requested, the request should (1) look for index.html within that directory and return that if it 
exists, and (2) if not, return a 404 error. Your server should return the correct MIME types for some 
basic file formats, based on the extension (.jpg, .gif, .png, .txt, .html). Keep in mind that image files must 
be sent in binary/byte stream form -- not with println or equivalent -- otherwise the browser will not be able 
to read them. If a GET or HEAD request is made that is not a valid UNIX path specification, if no file is 
found, or if the file is not accessible, you should return the appropriate HTTP error. See the HTTP Made 
Really Easy paper for more details.
MAJOR SECURITY CONCERN: You should make sure that users are not allowed to request absolute 
paths or paths outside the static file root directory. We will validate, e.g., that we can't get hold of 
/etc/passwd!
3.1. Expected level of HTTP protocol support
Your application server must be HTTP 1.1 compliant, and it must support all the features described in 
HTTP Made Really Easy. However:
● Persistent connections are suggested but not required for HTTP 1.1 servers. If you do not wish 
to support persistent connections, be sure to include “Connection: close” in the header of the 
response. 
● Chunked encoding (sometimes called chunking) is also not required. Support for persistent 
connections and chunking is extra credit, described near the end of this assignment.
HTTP Made Really Easy is not a complete specification, so you will occasionally need to look at RFC 2616 
(the 'real' HTTP specification; http://www.ietf.org/rfc/rfc2616.txt) for protocol details. Note that 
a well-defined protocol and precisely matched protocol implementation is really important! The majority 
of web developers’ headaches come from small differences in interpretation of the spec. Thus, it is 
important to think through every small detail including capitalization effects and what to do on
invalid inputs. If you have a protocol-related question, please make an effort to find the answer in the spec 
before you post the question to Piazza!
3.2. Special URLs
Your application server should implement two special URLs. If someone issues a GET /shutdown, your 
server should shut down immediately after any threads handling requests have finished. If someone issues 
a GET /control, your server should return a 'control panel' web page, which must contain at least a) a list 
of all the threads in the thread pool, b) the status of each thread ('waiting' or the URL it is currently 
handling), and c) a button that shuts down the server, i.e., is linked to the special /shutdown URL. It must 
be possible to open the special URLs in a normal web browser.
3.3 Core Requirements
For efficiency, your application server must be implemented using a thread pool that you implement, as 
discussed in class. Specifically, there should be one thread that listens for incoming TCP requests and 
enqueues them, and some number of threads that process the requests from the queue and return the 
responses. We will examine your code to make sure it is free of race conditions and the potential for 
deadlock, so code carefully! 
3.3.1. Standard API
You must also implement the standard interface, which we’ll be using to test. As in real systems, non￾conforming code is incorrect code! Refer to the Figure in Section 1.2 for the overall “flow.”
● The factory: WebServiceFactory. Just as developers statically import spark.Spark.* in the 
Spark Framework and use it to configure routes and server behavior, developers using your Web 
server framework will statically import edu.upenn.cis.cis455.WebServiceFactory. This 
class is used by the developer’s main program to control the Web services. It establishes the port, 
routes, etc., via functions such as get(), post(), etc.
● The WebService, which encapsulates an HttpListener listening on a specific port. It is the 
actual code that registers the route-handling and configuration calls such as get(), post(), etc., 
and it sets up the HttpListener and other code as appropriate.
● The objects to capture the HTTP Request and Response. We have a first version of these in 
…m1.interfaces, which have a subset of the whole API appropriate for Milestone 1. A second, 
more complete version appears in …m2.interfaces, e.g., handling cookies, form parameters, etc. 
You’ll need to develop your own implementations.
3.3.2. Provided Skeleton Code Whose API You Don’t Need to Maintain
We’ve also given you a bunch of starter code to help establish the main components of the project. These 
are all for your own internal use, as opposed to public APIs, so you may change the function interfaces. 
Note that some of our sample code does call functions here -- so you would need to change accordingly.
● HttpListener: The actual HTTP server with thread pool and thread management. This class 
should create and manage the thread poll, listen on sockets, and invoke handlers.
● HttpTask -- this is a “unit of work” for a Web service request handler, encompassing the data for 
a request.
● HttpWorker -- this is the actual Web service “worker” that takes an HTTP request, parses it 
(using HttpIoHandler), and sends responses (or exceptions).
● HttpTaskQueue -- this is intended to be the concurrency-protected queue between the server and 
the workers.
● HttpIoHandler -- this processes incoming requests on the socket, and sends outgoing responses. 
For now there is a (completely stubbed!) sendException() method.
We also provide some helper methods for parsing HTTP requests, in the …m1.handling package.
4. Milestone 2: Microservices Framework
The second milestone will build upon the Web server from Milestone 1, with support for building HTTP 
services, using the full routing framework model based on the Spark Framework interfaces. Basically, your 
first Milestone built a static HTML server, and your second Milestone builds out the full microservices 
framework.
● As a first step, you should probably checkpoint your Milestone 1 implementation in case you want 
to revisit it during regrades. Afterward, you should change the m1 imports in WebServiceFactory
to use m2. ● A developer should be able to register Route handlers, Filters, and the like from the 
WebServiceFactory.
○ Recall that one registers a route handler by calling post(path, route) for a POST 
request, get(path, route) for a GET request, etc. The path may actually include a 
pattern with wildcards (see the description in the Spark Framework documentation). The 
handler takes a Route (which in turn has a handle method that takes a Request and a 
Response).
○ A filter is called before (or after) the route handler, and is typically there to prevent 
certain requests from being handled (by throwing a HaltException, which is caught 
before the call to the route handler) or to add parameters to the HTTP request (giving 
more detail for the route handler).
Filters are commonly used to validate that a request is authorized (e.g., it belongs to an 
active session) before the route handler is called. See, for an example, the before()
filter in this Spark Framework example. ● You are expected to fully implement objects that provide the functionality hinted at with the 
...m2.interfaces “stub” classes (modeled after Spark Framework). Users should be able to 
create and query for Cookies, create Sessions that expire if not renewed, and process HTTP form 
and GET parameters.
● If a route handler throws a HaltException your server should catch the exception, and return an 
appropriate HTTP response with the error code and message.
● If a route handler calls redirect() in the response, your server should send an HTTP redirect 
message (a 301 or 302 code, as specified in the HTTP spec) to forward the browser to a new 
location.
● When creating a session, your session token must not be guessable: the client can ‘fake’ the session 
ID in a cookie. 
● For routes with wildcards, you may assume that a wildcard only matches a single ‘step’ between 
slashes. See extra credit for a more general solution. 
Additionally:
● You should build test cases for the new functionality, and develop tests to validate parsing, 
responses, handlers, etc.
● Logging messages from Log4j should be saved to a log file. You should be sure to use the INFO 
log level to track user requests for URLs.
To help you get started (and to form a reference specification), we have given you some additional, more 
fleshed-out classes, e.g., for requests, responses, and servers in the m2 “package tree.” You’ll note that most 
of these are subclassed from the m1 branch, but include functions for manipulating cookies, parameters, 
sessions, etc. Your Milestone 2 should completely implement the APIs here.
You’ll likely get a lot more insight into how the route handlers should work by looking at example 
applications for the Spark framework. Be careful to focus on the Spark Framework parts -- don’t let the 
Kotlin examples or the Spring code distract you from the key points!
4.2. Special URLs
You should now augment the /control URL from Milestone 1, to provide a way to view the Web server’s 
error log. It may provide other (e.g., extra-credit) features as you see fit.
5. Extra Credit
5.1. Persistent HTTP connections and chunked encoding (+10%, due with MS2)
We do not require persistence or chunking in your basic HTTP 1.1 server. However, each of these will 
count for part of up to 10% extra credit. To get full credit for chunking support, your server needs to be 
able to both send and receive chunked messages.
5.2. Performance testing (+5%, due with MS2)
Create a route handler with a computationally intensive task, which takes several seconds to perform on a 
modern computer. Experimentally determine the effect of changing the thread pool size on performance 
of the application server when many requests for your route handler come in at the same time. Comment 
on any trends you see and try to explain them. Suggest the ideal thread pool size and describe how you 
chose it. Include performance measures like tables, graphs, etc. Submit this with Milestone 2 as 
performance.pdf.
5.3. Multiple simultaneous sockets / servers (+15%, due with MS2)
The project described above loads one web application and installs it at the default port of 45555. Extend 
it to allow for multiple ports, optionally each with different routes (Add options to the main menu of the 
server to list installed applications, install new applications, and remove any installed applications. You’ll 
need to take special care to ensure that static variables do not get shared between applications (i.e. the same 
class in two different applications can have different values for the same static variable). Each application 
should have its own servlet context as well. (Since each application may have its own classpath, be sure to 
add the capability to dynamically modify the classpath, too.)
5.4. General wildcards in routes (+5% EC) 
For simplicity we are assuming routes can only have single step wildcards, i.e., a route wildcard has no 
slashes in it. More generally, the Spark Framework allows for multistep wildcards. Implement this more 
general approach and add support for the splat() function in the Request interface. 
6. Additional Requirements and Submission
Before you submit, you should test your code extensively. The code must contain a reasonable amount of 
useful documentation and at least 5 new test cases. You’ll submit each of the milestones separately, 
following the guidelines below.
Create a README file with information about any extra credit you did, how to run such extra credit 
functions, and any other comments you want to add.
6.1. Submitting Your Homework
Now you will need to create a Zip file.
● Go to the Terminal in Vagrant and type in:
cd /vagrant/555-hw1
zip ../hw1-m1.zip -r pom.xml README src/*
# or -m2 for milestone 2
Submit your zip file to Canvas just like you did with Homework 0. The autograder will provide some 
simple feedback after you submit but note that these are just very basic functionality. We will run many 
additional tests and you should too!
联系我们
  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-21:00
  • 微信:codinghelp
热点标签

联系我们 - QQ: 99515681 微信:codinghelp
程序辅导网!