Well it isn't actually RESTful unless you use it for that but that was the goal of this project. You can easily write request handlers that handle different request methods in the way you want.
This project use plain Java socket to implement the HTTP server, no bloat external dependencies. So that might make this server less competent and more insecure. True, but it was made to simply test your other web apps you write and that it is able to do. At the moment of writing the stand alone, runnable, binary jar is around 100 kBytes.
It also contains a HTTP Client that can HEAD, GET, PUT, POST, DELETE, TRACE, OPTIONS, PATCH, CONNECT to any url.
New from version 1.1.0 is the ability to put load on a service with a simple json load description file. Read more in the RelaxClient section below.
The RelaxServer is built to be easy to build and run. It is versatile and can handle all tasks that is thrown upon it. Built in are a DefaultFileHandler that can serve files from a given folder and its subfolders. It is the DefaultFileHandler that is used when the RelaxServer is started from its jar.
When built with maven using the maven-dependency-plugin the jar is a runnable server in its own right.
mvn clean package shade:shade
You will find the compiled artifact in the target
directory.
java -jar relax-n.n.n.jar
In this fashion it will serve your files in the current directory on localhost:8080 with 10 threads in the pool. There is also possible to change the server port, path to another directory and/or the number of threads in the fixed thread pool by adding one, two or all of the parameters:
- port=1234
- path=/absolute/or/relative/path/to/a/directory
- threads=20
- execute=jsonfile (Read more in the RelaxClient section below)
- header=key:value (Can be used multiple times)
- get=url (Make a get request)
- handlerclass=package.and.classname (External class has to be in classpath, can be used multiple times)
java -jar relax-n.n.n.jar port=1234 path=/absolute/or/relative/path/to/a/directory threads=20
Now you can browse to http://localhost:1234 in your favorite browser and see the files in the pointed out directory.
By putting you can add files:
curl -XPUT -T a-file-to-put.gif http://localhost:1234/the-name-of-the-file-when-uploaded.gif
Any file works, binary as well as text. Please note that using ports below 1024 will need root privileges on a unix environment.
The runnable jar is using the SimpleLogger from slf4j and that logs any INFO logs by default.
This can be adjusted by the standard Java VM argument for the SimpleLogger which is
-Dorg.slf4j.simpleLogger.defaultLogLevel=WANTED_LEVEL
where the WANTED_LEVEL can be any of these:
error, warn, info, debug or trace.
This will make the server quite quiet:
java -Dorg.slf4j.simpleLogger.defaultLogLevel=error -jar relax-n.n.n.jar
If you want the info logs but directed into a file you do:
java -jar relax-n.n.n.jar 2> filename.log
The requirements is that you have java 7 and maven installed for this to work.
First build the relax project with maven like this:
mvn clean install
This action will put the artifact in your local .m2/repository
Include it using dependency statement in your pom.xml like this:
<dependency>
<groupId>se.romram</groupId>
<artifactId>relax</artifactId>
<version>n.n.n</version>
</dependency>
By including the relax jar in your own project, see above, you can build a Hello World HTTP server as easy as this:
RelaxServer server = new RelaxServer(8080, new RelaxHandler() {
public boolean handle(RelaxRequest request, RelaxResponse response) {
response.respond(200, "Hello World!");
return true;
}
});
server.start();
At this point you can browse http://localhost:8080 to see the response. Since no action is taken to distinguish between different paths the text 'Hello World!' will always be returned.
The RelaxServer class has a set of methods that all return the current object instance of the class which means that the methods can be placed in a chain:
RelaxServer server = new RelaxServer(8080, new RelaxHandler() {
public boolean handle(RelaxRequest request, RelaxResponse response) {
response.respond(200, "<html><body><h1>Hello World!</h1></body></html>");
return true;
}
});
server
.setExecutor(Executors.newFixedThreadPool(30))
.addHeaders("Server: RelaxServer")
.setContentType("text/html")
.start();
We have added a different thread pool than the default which is fixed at 10 threads in the pool.
One header has been added. Several headers can be added in the same method. Just separate them with a comma.
The content type is also set to text/html this time instead of the default text/plain, since we return HTML tags this time.
To be usable as a stand alone web server the RelaxServer has two built in handlers.
The most usable is the DefaultFileHandler that is responsible for returning files from your filesystem as requested. If nothing is requested or if you select a directory a file listing is returned. If the user agent tells the server that it can handle HTML that is returned with working links otherwise the listing is returned as plain text.
New from version 1.4 is the ability to add the header Accept: application/json
or query parameter
accept=json
to get the listing as json. As a side effect you can add accept=plain
to get the
response as plain text in your browser.
NB!! Hidden files are not returned.
Go to this url http://localhost:8080
with both your favourite web browser and cUrl to see
the difference. Test curl -H 'Accept: application/json' http://localhost:8080
to receive json.
To be able to give a web browser the favicon it always asks for this handler is always added to the list of handlers but it is placed at the end.
This means that you can easily replace the favicon with your own should you want that.
Another built in handler manages a few tasks that can come in handy.
First you can ask for serverstats
which returns some statistics of the server.
It is returned as json and can look like this:
{
server: "RelaxServer",
pid: "16773",
port: 8080,
activeThreads: 2,
requestCount: "33065",
os: {
name: "Linux",
arch: "amd64",
version: "3.8.0-34-generic"
},
sysload: "3.34",
cors: 4,
process: {
mem%: 1,
peekTime: 505,
sharedMem: 10000000,
residentMem: 257000000,
cpu%: 72
},
java: {
name: "Java HotSpot(TM) 64-Bit Server VM",
arch: "Oracle Corporation",
version: "1.7.0_76"
}
}
Most of these data items is collected from the underlying OS. The sysload value is an system overall value. Process values is only returned on a Linux platform and are in bytes and milliseconds where applicable.
The cpu% item tells the cpu utilization of this process only as do the other values within the process element.
NB!! Peeking into process is not possible using java 9 at the moment.
This example is a simple http proxy.
// Simple HTTP Proxy implementation
public static void main(String[] args) throws IOException {
final RelaxServer relaxServer = new RelaxServer(8080, new RelaxHandler() {
@Override
public boolean handle(RelaxRequest relaxRequest, RelaxResponse relaxResponse) {
String url = relaxRequest.getRequestURL();
RelaxClient relaxClient = new RelaxClient().get(url);
String accept = relaxRequest.getHeaderMap().get("Accept");
relaxResponse.setContentType(accept != null && accept.contains("text/css") ? "text/css" : "text/html");
relaxResponse.respond(relaxClient.getStatus().getCode(), relaxClient.getBytes());
return true;
}
});
relaxServer.start();
}
The RelaxClient can be used from inside a Java program or via the load test feature. It is very easy to use and has a lot of features that can be added.
The following code fetches the web page from Google.
String response = new RelaxClient().get("http://google.com").toString();
However the RelaxClient response contains more features than just the response in form of a String:
RelaxClient client = new RelaxClient().get("http://google.com");
if (client.getStatus().isOK()) {
log.debug(client.getStatus().toString());
}
Most of the methods that are available to the RelaxClient returns the instance object of the class, it is therefore very easy to chain them together.
RelaxClient client = new RelaxClient()
.throwExceptions()
.useDefaultCookieManager()
.get("http://google.com");
if (client.getStatus().isOK()) {
log.debug(client.getStatus().toString());
}
Here we added throwExceptions()
which means that if something goes wrong a
Runtime Exception Descendant will be thrown.
This is for those that want to recover from an error in that way.
Otherwise it just logs the error.
We also added useDefaultCookieManager()
that manages cookies received.
Since version 1.1.0 of the Relax project you can make simple load testing just by writing a small json file.
{
"tasks": [
{
"url": "http://www.google.com"
}
]
}
This is as small as the load test json can be. It will not put any heavy load on the service since no loops are defined.
java -jar relax-n.n.n.jar execute=google-first-load.json
The response to this will be two printout rows in the console, among others, that looks like this:
timeStamp,elapsed,label,responseCode,responseMessage,success,grpThreads,allThreads,Latency
1425281402416,120,null,200,OK,true,1,1,120
For you out there that are familiar with JMeter will notice the resemblance. It is intentional since you can use these printouts to get nice reports from http://loadosophia.org and others.
Relax is not intended to place heavy load on services and produce trustworthy reports. This feature was added to be able to place background-noise-load on your services to easily notice how they behave when used by many.
This example shows all the features of the relax load json and is an realistic example of 100 users using our server at the same time, We have defined a base loop of -1 which means loop forever:
{
"virtualUsers": 100,
"rampUp": 10,
"loop": -1,
"tasks": [
{
"name": "Start call",
"loop": 1,
"url": "http://localhost:8080",
"validate": {
"contains": ["pom.xml", "!filenameNotToBeFound"],
"status": [200]
},
"delay": 2000
},
{
"name": "Error page",
"loop": 1,
"url": "http://localhost:8080/404",
"validate": {
"status": [404]
},
"delay": 3000
},
{
"name": "Server stats",
"active": true,
"loop": 1,
"url": "http://localhost:8080/serverstats",
"validate": {
"contains": ["^\{", "cpu%.:", "peekTime.:.*\d{1,3}"],
"status": [200]
},
"delay": 10000
}
]
}
In this case every loop takes 15 seconds and has three requests. The first request has a delay of two seconds, the second request has a delay of three seconds and the last has a delay of 10 seconds.
The full loop takes around 15.5 seconds since the Server stats request itself takes 0.5 seconds. This corresponds to a per user tps (Transaction Per Second) of 3/15.5, approx 0.194.
Since we are using 100 virtual users we have a constant load of around 19.4 tps. This is a cpu% load on the service of 15 % on a 4 core system. Quite a nice background noise.
In the file above all features has been used but not necessarily to its extent. Here is an explanation on all available parameters:
- virtualUsers: numeric value. Best practice is to not extend 200.
- rampUp: time in seconds until all users are up.
- loop: -1=forever or as many as you want.
- tasks: an array of task objects. No limit.
- task.name: The name of the task in the log.
- task.active: Flag to inactive task, e.g. during test phase.
- task.loop: task independent loop.
- task.url: the url to fetch.
- task.validate.contains: an array of reg.exp strings that should match and in case the reg.exp starts with ! reg.exp that should NOT match.
- task.validate.status: an array of acceptable statuses. Any one should match.
- task.delay: delay in milliseconds to pause after the request.
As mentioned above it is possible to take the output from relax and upload that to the JMeter report generator site loadosophia.org.
Since the error log and other output gets mixed you need to make use of file descriptor redirect feature that you get from a unix type system.
java -Dorg.slf4j.simpleLogger.defaultLogLevel=error \
-jar target/relax-n.n.n.jar execute=src/test/resources/load.json \
2> errors.log 1> result.csv
In the above call we set the error level to error although it is not strictly necessary. But it keeps the errors.log file small.
We execute the load.json file that is included in the project.
The last row does the redirect:
- 2> errors.log - sends the error log to that file
- 1> result.csv - sends std out to the file result.csv
When done the result.csv file can be uploaded to loadosophia.org for processing.