1. Setup Tomcat Server
1.0 Why we need HTTP protocal?
- Set the same format patten for both local and server end to help communication.
- (req/ res) Header & a blank line to seperate & (req/ res) message body.
- Http methods support all CRUD operations. 为了区分idempotent(幂等操作) operations: 幂等 用put/ get; 非幂等 用post/delete;
1.1 Concept of RESTful API
- In this project: 1) We plan to use the HTTP methods to indicate what kind of operation we want to take. The reason is that if the operation are directly based on HTTP methods, then the server could get it directly without parsing extra things. 2) Use HTTP URL to indicate which service we want to use/ what data we want to request. That will be easy for client/users to understand. 3) Each request will be independent, which ensures the server is running in the stateless mode and improve the scalability.
1.2 Concept of Tomcat/ RPC/ Java Servlet
- Tomcat Server is an open-source Java Servlet Container that provides “pure Java” http wep server environment (including low level supports: making TCP connection/ revceiving request from client/ send response back to client…) in which Java code can run. 帮忙做http 请求的分发。
- RPC (Remode Procedure Call) is a function call to the remote server.
- Java Servlets are Java classes to handle RPC calls on the server side.
1.3 Setup Tomcat Server within Eclips
- Go to
Servers
, findApache
->Tomcat v9.0 Server
and specify the path where the Tomcat installation package was downloaded. Then the Tomcat v9.0 Server will be at localhost. - Double Click ‘Tomcat v9.0 Server at localhost’ in Server window. In ‘Server Locations’, click ‘Use Tomcat installation’. Save Config.
- Right click ‘Tomcat v9.0 Server at localhost’, choose ‘Properties’. Click ‘Switch Location’ to change the location to /Servers/Tomcat v9.0 Server at localhost.server.
- Start Tomcat Server, check connection on Port: 8080.
2. Implement RPC Handlers
2.1 Implement the SearchItem servlet (class)
Create the first servlet/class
SearchItem
. Eclipse creates the Java class for us automatically. Then we need to change the endpoint to “/search”.1
"/search") (
Within this project, server and client need to communicate by JSON object format. To enable this, we need to download the JSON library .jar file and paste it into
WEB-INFO
->lib
folder.
2.2 Implement the RecommendItem servlet (class)
- Create the servlet/class
RecommendItem
, then we change the endpoint to “/recommendation”.1
"/recommendation") (
3. Fetch data from TicketMaster API
3.1 Create a RpcHelper Class to help write Json Array/ Obj.
Create a new class in rpc, define the
writeJsonArray()
function andwriteJsonObject()
function inside RpcHelper class.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class RpcHelper {
...
// Writes a JSONArray to http response.
public static void writeJsonArray(HttpServletResponse response, JSONArray array) throws IOException {
response.setContentType("application/json");
response.setHeader("Access-Control-Allow-Origin", "*");
PrintWriter out = response.getWriter();
out.print(array);
out.close();
}
// Writes a JSONObject to http response.
public static void writeJsonObject(HttpServletResponse response, JSONObject obj) throws IOException {
response.setContentType("application/json");
response.setHeader("Access-Control-Allow-Origin", "*");
PrintWriter out = response.getWriter();
out.print(obj);
out.close();
}
}Update the
doGet()
function in both SearchItem and RecommendItem Servlet to use these helper functions.1
2
3
4
5
6
7
8
9
10
11
12protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
JSONArray array = new JSONArray();
// test with some dummy data here
try {
array.put(new JSONObject().put("username", "abcd").put("address", "San Francisco").put("time", "01/01/2017"));
array.put(new JSONObject().put("username", "1234").put("address", "San Jose").put("time", "01/02/2017"));
} catch (JSONException e) {
e.printStackTrace();
}
RpcHelper.writeJsonArray(response, array);
}
3.2 Look into TicketMaster API
- TicketMaster is a web-based API from which users could get real event informations. In this project, data will be fetched from TicketMaster API. For security reasons, TicketMaster requires users to register their own uniqe API key, so that they know who is fetching data from their API.
- Firstly, we need to look through the Documents provided by TicketMaster to undetstand how datas are stored in thier object structure.
- According to thier settings, the endpoint for event searching is
/discovery/v2/events
, Therefore, the request URL will be something like:https//app.ticketmaster.com/discovery/v2/events.json?apikey=<YOUR_UNIQUE_API_KEY>&latlong=<YOUR_LAT,YOUR_LNG>&keyword=<YOUR_SEARCH_KEY_WORD>&radius=<SEARCH_RADIUS>
.
3.3 Get data from TicketMaster API.
Create a new Package named
external
, within this package create a classTicketMasterAPI
for calling the TicketMaster API. Some constants are needed to be updated here.1
2
3
4
5
6
7
8
9
10
11
12
13
14package external;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import org.json.JSONArray;
import org.json.JSONObject;
public class TicketMasterAPI {
private static final String URL = "https://app.ticketmaster.com/discovery/v2/events.json";
private static final String DEFAULT_KEYWORD = ""; // no restriction
private static final String API_KEY = "USE_YOUR_OWN_KEY";
}Add
search()
method to calling the TicketMaster and getting datas, and add another test methodqueryAPI()
to temporaryly print the search result for debugging. Create a main functionmain()
to callqueryAPI()
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73public class TicketMasterAPI {
private static final String URL = "https://app.ticketmaster.com/discovery/v2/events.json";
private static final String DEFAULT_KEYWORD = ""; // no restriction
private static final String API_KEY = "USE_YOUR_OWN_KEY";
public List<Item> search(double lat, double lon, String keyword) {
if (keyword == null) {
keyword = DEFAULT_KEYWORD;
}
try {
keyword = URLEncoder.encode(keyword, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String query = String.format("apikey=%s&latlong=%s,%s&keyword=%s&radius=%s", API_KEY, lat, lon, keyword, 50);
String url = URL + "?" + query;
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("Sending request to url: " + url);
System.out.println("Response code: " + responseCode);
if (responseCode != 200) {
return new ArrayList<>();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
JSONObject obj = new JSONObject(response.toString());
if (!obj.isNull("_embedded")) {
JSONObject embedded = obj.getJSONObject("_embedded");
return embedded.getItemList("events");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new ArrayList<>();
}
// Convert JSONArray to a list of item objects.
private List<Item> getItemList(JSONArray events) throws JSONException {
List<Item> itemList = new ArrayList<>();
return itemList;
}
private void queryAPI(double lat, double lon) {
List<Item> events = search(lat, lon, null);
for (Item event : events) {
System.out.println(event.toJSONObject());
}
}
/**
* Main entry for sample TicketMaster API requests.
*/
public static void main(String[] args) {
TicketMasterAPI tmApi = new TicketMasterAPI();
// Give a dummy query location for testing: (Mountain View, CA)
tmApi.queryAPI(37.38, -122.08);
}
}
3.4 Purify the data return from TicketMaster API.
In this project, we only need some fields within all fields return by TicketMaster, so that’s the reason why we need to purify it before send it back.
Create a new package
entity
to hold data for us. Within this package, new a classItem
.1
2
3
4
5
6
7
8
9
10
11
12
13package entity;
import java.util.Set;
public class Item {
private String itemId;
private String name;
private double rating;
private String address;
private Set<String> categories;
private String imageUrl;
private String url;
private double distance;
}Add Getters and Setters. In
Source
->Generate Getters and Setters
by clicking Select Getters, Eclips will automatically generate all getters for us.Then, we need to add a
toJSONObject()
method, since we need to convert item datas into JSON format to send to the frontend.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public JSONObject toJSONObject() {
JSONObject obj = new JSONObject();
try {
obj.put("item_id", itemId);
obj.put("name", name);
obj.put("rating", rating);
obj.put("address", address);
obj.put("categories", new JSONArray(categories));
obj.put("image_url", imageUrl);
obj.put("url", url);
obj.put("distance", distance);
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
3.5 Build a Builder Pattern to easily handle thc construction of a complex object.
Within class
Item
, add a static new classItemBuilder
and move all fileds into this class. Generate setters for all data fields in this ItemBuilder.To use this builder pattern, we create a private constructor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76public class Item {
private String itemId;
private String name;
private double rating;
private String address;
private Set<String> categories;
private String imageUrl;
private String url;
private double distance;
public static class ItemBuilder {
private String itemId;
private String name;
private double rating;
private String address;
private Set<String> categories;
private String imageUrl;
private String url;
private double distance;
// Generate Setters for all fields
public ItemBuilder setItemId(String itemId) {
this.itemId = itemId;
return this;
}
public ItemBuilder setName(String name) {
this.name = name;
return this;
}
public ItemBuilder setRating(double rating) {
this.rating = rating;
return this;
}
public ItemBuilder setAddress(String address) {
this.address = address;
return this;
}
public ItemBuilder setCategories(Set<String> categories) {
this.categories = categories;
return this;
}
public ItemBuilder setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
return this;
}
public ItemBuilder setUrl(String url) {
this.url = url;
return this;
}
public ItemBuilder setDistance(double distance) {
this.distance = distance;
return this;
}
// define a build function to create a ItemBuilder object
public Item build() {
return new Item(this);
}
}
/**
* This is a builder pattern in Java.
*/
private Item(ItemBuilder builder) {
this.itemId = builder.itemId;
this.name = builder.name;
this.rating = builder.rating;
this.address = builder.address;
this.categories = builder.categories;
this.imageUrl = builder.imageUrl;
this.url = builder.url;
this.distance = builder.distance;
}
...
}According to the body structure, we need to add multiple helper functions to fetch data which are included deeply in TicketMaster API response body.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71public class TicketMasterAPI {
...
private String getAddress(JSONObject event) throws JSONException {
if (!event.isNull("_embedded")) {
JSONObject embedded = event.getJSONObject("_embedded");
if (!embedded.isNull("venues")) {
JSONArray venues = embedded.getJSONArray("venues");
for (int i = 0; i < venues.length(); ++i) {
JSONObject venue = venues.getJSONObject(i);
StringBuilder builder = new StringBuilder();
if (!venue.isNull("address")) {
JSONObject address = venue.getJSONObject("address");
if (!address.isNull("line1")) {
builder.append(address.getString("line1"));
}
if (!address.isNull("line2")) {
builder.append(",");
builder.append(address.getString("line2"));
}
if (!address.isNull("line3")) {
builder.append(",");
builder.append(address.getString("line3"));
}
}
if (!venue.isNull("city")) {
JSONObject city = venue.getJSONObject("city");
builder.append(",");
builder.append(city.getString("name"));
}
String result = builder.toString();
if (!result.isEmpty()) {
return result;
}
}
}
}
return "";
}
private String getImageUrl(JSONObject event) throws JSONException {
if (!event.isNull("images")){
JSONArray images = event.getJSONArray("images");
for (int i = 0; i < images.length(); ++i) {
JSONObject image = array.getJSONObject(i);
if(!image.isNull("url")){
return image.getString("url");
}
}
}
return "";
}
private Set<String> getCategories(JSONObject event) throws JSONException {
Set<String> categories = new HashSet<>();
if (!event.isNull("classifications")) {
JSONArray classifications = event.getJSONArray("classifications");
for (int i = 0; i < classifications.length(); ++i) {
JSONObject classification = classifications.getJSONObject(i);
if (!classification.isNull("segment")) {
JSONObject segment = classification.getJSONObject("segment");
if (!segment.isNull("name")) {
categories.add(segment.getString("name"));
}
}
}
}
return categories;
}
}
3.6 Connect the SearchItem servlet to TicketMasterAPI
- Update the
doGet()
method within theSearchItem
class.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class SearchItem extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
double lat = Double.parseDouble(request.getParameter("lat"));
double lon = Double.parseDouble(request.getParameter("lon"));
TicketMasterAPI tmAPI = new TicketMasterAPI();
List<Item> items = tmAPI.search(lat, lon, null);
JSONArray array = new JSONArray();
for (Item item : items) {
array.put(item.toJSONObject());
}
RpcHelper.writeJsonArray(response, array);
}
...
}