Rest API Example
Rest API Example
io/tutorials/java-
server/rest-api
Creating a REST API
tutorial java server rest ajax json
REST
Simple Example REST API
Simple Java REST Client
Simple JavaScript REST Client
CORS
Handling Logins in REST
o Authorization Header
Token Authentication
Summary
Homework
Now we know how to create a web app using servlet classes. We know how to
get user input, how to access a database, and how to handle user logins. But
what if we want to support different kinds of programs instead of just a web app?
What if we want to create a desktop application or an Android app? How do we
provide access to our data for those programs without writing everything from
scratch each time?
This tutorial introduces the idea of creating a REST API, which is a way of
organizing our code so we can access our data from multiple applications. Your
REST API is server code whose job it is to provide access to your data and to
enforce rules like who can see what. Then other programs use your REST API to
interact with your data.
This high-level diagram shows how you might organize your code: you’d have a
database (or multiple databases), and your REST API would sit on top of that. It
would use SQL and JDBC to interact with the database, exactly like we’ve
already learned about. Then other applications would call your REST API, which
lets you centralize all of your core logic in one place instead of rewriting it every
time you wanted to create a new application.
To put it another way: a REST API is just a web app, very similar to all of the web
apps we’ve already built. The only difference is that instead of showing a website
for a GET request, it provides data. And instead of using HTML forms to create
a POST request, it takes POST requests from other applications! (Of course, one of
those applications could be another web app that gets user input using HTML
forms!)
REST
REST stands for representational state transfer, which is just a fancy name for a
set of rules that you can follow to build a web app that provides access to data in
a way that’s reusable by multiple applications, or many users of the same
application. REST doesn’t actually involve any new technical concepts. It’s more
a way of using the concepts we’ve already learned.
import java.util.HashMap;
import java.util.Map;
/**
* Example DataStore class that provides access to user data.
* Pretend this class accesses a database.
*/
public class DataStore {
This example uses a Map to store data in memory, but this could just as easily use
a database connection. The point is that this class provides access to our data. It
uses a simple Person class:
This is just a plain old Java class that contains variables and functions for
accessing those variables. Now we can create our servlet class:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws IOException, ServletException {
if(person != null){
String json = "{\n";
json += "\"name\": " +
// the method is used to escape quotes
JSONObject.quote(person.getName()) + ",\n";
json += "\"about\": " +
JSONObject.quote(person.getAbout()) + ",\n";
json += "\"birthYear\": " + person.getBirthYear() + "\
n";
json += "}";
response.getOutputStream().println(json);
}
else{
//That person wasn't found, so return an empty JSON
object. We could also return an error.
response.getOutputStream().println("{}");
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws IOException, ServletException {
This servlet class contains a doGet() function that gets a person’s name from the
URL, and then uses the DataStore class to fetch that person. It then creates a
JSON string from that person’s data, and returns that JSON as the response to
the GET request. (If you don’t remember JSON, check out the JSON tutorial.) This
code uses the json.org Java library to escape the String values before adding
them to our JSON. This is important! Remember that JSON uses keys and
values surrounded by quotation marks, like this:
{
"name": "Ada",
"about": "Ada Lovelace was the first programmer.",
"birthYear": 1815
}
But what if name or about contain a quotation mark? We’d have something like
this:
{
"name": "Ada",
"about": "This contains " a quote",
"birthYear": 1815
}
This is not valid JSON, and you’ll get an error when you try to parse it. We need
to escape the quotation mark so it looks like this:
{
"name": "Ada",
"about": "This contains \" a quote",
"birthYear": 1815
}
<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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>PersonServlet</servlet-name>
<servlet-class>PersonServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PersonServlet</servlet-name>
<url-pattern>/people/*</url-pattern>
</servlet-mapping>
</web-app>
This maps any URL that starts with /people/ to our servlet. Try running this on a
local server and then navigating to http://localhost:8080/people/Ada in your
browser. You should see the JSON representation:
This isn’t very exciting, but that’s not the point. You’re not really supposed to view
a REST API in a browser anyway. The whole point is that you can use this data
to build other applications!
connection.setRequestMethod("GET");
return response;
}
// an error happened
return null;
}
connection.setRequestMethod("POST");
connection.setDoOutput(true);
OutputStreamWriter wr = new
OutputStreamWriter(connection.getOutputStream());
wr.write(postData);
wr.flush();
This program asks the user whether they want to get or set user data. If they
want to get data, then the program gets a name from the user, and then calls
the getPersonData() function. That function uses the HttpUrlConnection class,
which is just a regular Java class available in the standard API. That class lets
you make requests to a server, and the getPersonData() function uses it to make
a GET request to our REST API (make sure your server is running when you run
this code). The response is the JSON our REST API outputs, which this client
program then parses using the JSON library to output to the command line.
If the user wants to set data, then the program gets a name, birth year, and an
about sentence from the user, and then calls the setPersonData() function. That
function uses the HttpUrlConnection class to send a POST request to our server.
The code uses the URLEncoder.encode() function to encode our String values, just
in case they contain characters like & ampersands which would break the format
of the data. Then our server handles that request and stores the data.
We now have two separate applications: our REST API running in a server, and
a client application that runs in the command line. We could use very similar
code to create a desktop application or an Android app, or even another server
that provides a user interface.
<!DOCTYPE html>
<html>
<head>
<title>PIJON</title>
<script>
function getPersonInfo(){
var name =
document.getElementById('name').value;
document.getElementById('birthYear').value = person.birthYear;
document.getElementById('about').value = person.about;
}
}
}
ajaxRequest.open('GET',
'http://localhost:8080/people/' + name);
ajaxRequest.send();
}
function setPersonInfo(){
var name =
document.getElementById('name').value;
var about =
document.getElementById('about').value;
var birthYear =
document.getElementById('birthYear').value;
This webpage shows three input boxes. If the user types a name and presses
the Get button, the getPersonInfo() function uses AJAX to make a GET request to
our REST API. It then parses the JSON response and populates the other text
boxes. If the user then modifies those text boxes and clicks the Save button,
the setPersonInfo() function uses AJAX to make a POST request to our REST API.
Try making a change in the JavaScript client and then viewing it in the command
line application!
CORS
Depending on your settings, you might have gotten an error when you tried to run
the above JavaScript. Something like:
<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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>PersonServlet</servlet-name>
<servlet-class>PersonServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PersonServlet</servlet-name>
<url-pattern>/people/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>cross-origin</filter-name>
<filter-
class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
<init-param>
<param-name>allowedOrigins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>allowedMethods</param-name>
<param-value>GET,POST</param-value>
</init-param>
<init-param>
<param-name>allowedHeaders</param-name>
<param-value>X-Requested-With,Content-
Type,Accept,Origin,Authorization</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>cross-origin</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Again, how you enable CORS for your server depends on which server you’re
using!
Handling Logins in REST
We’ve already learned that we can use sessions to store per-user information on
our server, which allows us to handle things like logins. But when creating a
REST API, every request should be standalone, which means we shouldn’t store
per-user information on the server. In other words, we shouldn’t use sessions to
track user logins in a REST server.
But what if we want to restrict access to some of our data? For example, what if
we only want a user to be able to edit their own information? If we could store
per-user data on the server, we could track whether a user was logged in. But
since we can’t store data on the server, we have to do something else.
We solve this problem by sending in the user’s login information (username and
password) with every request.
(Remember that in real life we wouldn’t store the password directly; we’d store a
hash of the password.)
Then let’s add some passwords to our fake dummy data in the DataStore class
In real life you’d get these passwords (again, the hashes of the passwords) from
users when they register.
Now this function gets the password from the request, and checks to make sure
it’s valid before it goes through with submitting the edit. If it’s not valid, it returns
an error code.
This is just a simple example to show you the basics, but the idea is the same for
more complicated projects: you pass in the login data with every request, and the
server checks it before doing any processing.
Authorization Header
http://localhost:8080/people/Ada?password=MyPasswordHere
Nothing is stopping us from doing exactly that, and it would work. But in general,
passing login information via the URL is considered a bad idea. The URL might
get cached or logged, which could allow hackers to get our login information. And
from a design perspective it’s a little messy. This is pretty subjective, but we’re
mixing login information with request information, which isn’t ideal. We want to
separate the request (the what) from information about the request (the how). In
other words, we don’t want to mix login information into our request URLs.
Instead of putting login information in the URL itself, most REST APIs use
an authorization header to handle login information. A request header allows a
request to include information about itself: stuff like the browser version and
cache data. The authorization header is just a username and a password that
goes along with a request.
We’re the ones writing the code, so how we represent the username and
password is up to us. But traditionally, it’s represented as a 64-bit encoded string.
That might sound complicated, but it’s just a way of storing the username and
password so it doesn’t mess up the formatting of the request, exactly like we’ve
already seen with URLEncoder.encode() and JSONObject.quote() in our code above.
We can do that using the Base64 class that comes standard with Java.
In a servlet function, first we get the authorization header:
This gets the part of the String that starts after the space, which gives us just the
encoded username and password. Next we need to decode the encoded value:
In our REST clients, we’d have to do the same thing, just in reverse:
This code takes a username and password and creates an authorization string
from them by simply combining them with a : colon in the middle. It then encodes
that authorization string in base 64. It then creates a header string by
adding Basic to the beginning, so the server knows to expect a username and
password. Finally, it sets the Authorization header to be sent to the server.
connection.setRequestMethod("POST");
connection.setDoOutput(true);
OutputStreamWriter wr = new
OutputStreamWriter(connection.getOutputStream());
wr.write(postData);
wr.flush();
Note that we’re using base 64 as an encoding, not as encryption! In other words,
using base 64 doesn’t make it harder for hackers to get at a username and
password! We only use it so we don’t break the formatting of the request with
special characters in the username or password. To prevent hackers from getting
the username and password, you need to use HTTPS and the other precautions
we’ve already covered.
Also note that you can create a request that uses the authorization header,
including the base 64 encoding, from JavaScript as well.
Token Authentication
With all of the above approaches, we need to send the username and password
with every request, which means that clients need to keep the username and
password in memory at all times. This gives hackers more opportunities to get at
usernames and passwords: they might find ways to hack our clients, or they
might find ways to look at our server logs, or any number of other attack vectors.
Instead of sending the username and password with every request, we can
submit them to our REST API just once, and have the REST API return what’s
called a token, which is just a random String value. The server stores that token,
along with which username it maps to, as well as an expiration time (usually
about 15 minutes in the future). The client then stores the token in memory
instead of the password, and sends the token along with every request. The
server uses that token to look up which username it maps to, and makes sure
that requests using that token only work until the expiration time.
With this approach, if an attacker gets the token, they only have 15 minutes to do
any damage. This is much better than a hacker getting a user’s password.
On the server side, we’d start by creating a way to store token information:
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Class that holds token data. In real life this might use a database.
*/
public class TokenStore {
/**
* Generates a token for the username, stores that token along with an
* expiration time, and then returns the token so clients can store
it.
*/
public String putToken(String username){
String token = UUID.randomUUID().toString();
tokenMap.put(token, new TokenData(username));
return token;
}
/**
* Returns the username mapped to the username, or null
* if the token isn't found or has expired.
*/
public String getUsername(String token){
if(tokenMap.containsKey(token)){
if(tokenMap.get(token).expirationTime >
System.currentTimeMillis()){
return tokenMap.get(token).username;
}
else{
//the token has expired, delete it
tokenMap.remove(token);
}
}
return null;
}
/**
* Internal class that holds a username and an expiration time.
*/
private static class TokenData{
String username;
long expirationTime;
Then we would create a servlet class that allows clients to post a username and
password to get a token:
import java.io.IOException;
import java.util.Base64;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Override
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws IOException, ServletException {
String authHeader = request.getHeader("authorization");
String encodedAuth = authHeader.substring(authHeader.indexOf('
')+1);
String decodedAuth = new
String(Base64.getDecoder().decode(encodedAuth));
String username = decodedAuth.substring(0,
decodedAuth.indexOf(':'));
String password =
decodedAuth.substring(decodedAuth.indexOf(':')+1);
Person loggedInPerson =
DataStore.getInstance().getPerson(username);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
This servlet class only contains a doPost() function, which gets the username and
password from the authorization header. It then checks that username and
password against the person data, and returns an error if it doesn’t match. If it
does match, it uses the TokenStore class to generate a token, and it returns the
token as the body of the response. Now clients can make a POST request to the
URL we map to this servlet to get a token.
Next, we need to make it so the rest of our API accepts a token for
authentication:
import java.io.IOException;
import java.util.Base64;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws IOException, ServletException {
if(person != null){
String json = "{\n";
json += "\"name\": " +
JSONObject.quote(person.getName()) + ",\n";
json += "\"about\": " +
JSONObject.quote(person.getAbout()) + ",\n";
json += "\"birthYear\": " + person.getBirthYear() + "\
n";
json += "}";
response.getOutputStream().println(json);
}
else{
//That person wasn't found, so return an empty JSON
object. We could also return an error.
response.getOutputStream().println("{}");
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Person loggedInPerson =
DataStore.getInstance().getPerson(username);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
loggedInPerson.setAbout(about);
loggedInPerson.setBirthYear(birthYear);
//if we made it here, everything is okay, save the user
DataStore.getInstance().putPerson(loggedInPerson);
}
}
Here’s a JavaScript client that interacts with our new token-based REST API:
<!DOCTYPE html>
<html>
<head>
<title>PIJON</title>
<script>
var name;
function login(){
name = document.getElementById('name').value;
var password =
document.getElementById('password').value;
document.getElementById('password').value = '';
function getPersonInfo(){
document.getElementById('birthYear').value = person.birthYear;
document.getElementById('about').value = person.about;
document.getElementById('login').style.display = 'none';
document.getElementById('content').style.display = 'block';
}
}
};
ajaxRequest.open('GET',
'http://localhost:8080/people/' + name);
ajaxRequest.setRequestHeader("authorization",
authHeader);
ajaxRequest.send();
}
function setPersonInfo(){
var about =
document.getElementById('about').value;
var birthYear =
document.getElementById('birthYear').value;
document.getElementById('content').style.display = 'none';
document.getElementById('login').style.display = 'block';
}
}
};
ajaxRequest.open('POST',
'http://localhost:8080/people/' + name);
ajaxRequest.setRequestHeader("authorization",
authHeader);
ajaxRequest.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
ajaxRequest.send(postData);
}
</script>
</head>
<body>
<h1>PIJON</h1>
<p>(Person Info in JavaScript Object Notation)</p>
<div id="login">
<p>Name: <input type="text" value="Ada" id="name"
/></p>
<p>Password: <input type="password" id="password"
/></p>
<p><button type="button"
onclick="login()">Login</button></p>
</div>
</body>
</html>
This JavaScript client first shows a login screen. When the user clicks
the Login button, this client makes a POST request to our REST API to get a token.
This allows the user to stay logged in, without the client keeping the password
around in memory. Then the edit screen is shown, where the user can edit their
own info. When they click the Save button, the token is sent in the authorization
header, and the server uses that for authentication. After fifteen minutes, the user
will have to login again to get a new token!
This is just a barebones example of a token-based REST API and a client. There
are a ton of enhancements you could make: you could send “token refresh”
requests so the user doesn’t have to login every fifteen minutes, or you could add
other endpoints (URLs) in your REST API for more functionality, or you could
make the clients look prettier. But these basics were meant to show you how a
token-based REST API works.
Summary
That was probably a lot to take in, but REST comes down to a few ideas:
Homework
Convert your server into a REST API, and then create two
different clients that interact with it.