1 package de.cweiske.ouya.louyapi;
3 import static de.cweiske.ouya.louyapi.HttpService.TAG;
5 import android.content.res.AssetManager;
6 import android.util.Log;
8 import java.io.IOException;
9 import java.io.InputStream;
11 import fi.iki.elonen.NanoHTTPD;
13 public class HttpServer extends NanoHTTPD {
15 private final AssetManager assetManager;
16 protected String prefix = "stouyapi-www";
18 public HttpServer(int port, AssetManager assetManager) {
20 this.assetManager = assetManager;
24 * This is basically a re-implementation of the stouyapi .htaccess file
26 * @param session The HTTP session
27 * @return HTTP response
29 public Response serve(IHTTPSession session) {
30 String path = session.getUri();
31 Log.d(TAG, "serve: " + path);
32 //this happens with "//agreements/marketplace.html". remove double slash.
33 if (path.startsWith("//")) {
34 path = path.substring(1);
39 if (path.equals("/api/v1/status") || path.equals("/generate_204")) {
40 //usage: check if internet connection is working
41 return newFixedLengthResponse(Response.Status.NO_CONTENT, null, "");
43 } else if (path.equals("/api/v1/details") && session.getParameters().containsKey("app")) {
44 //usage: detail page for installed games
45 if (null != (content = loadFileContent("/api/v1/details-data/" + session.getParameters().get("app").get(0) + ".json"))) {
46 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
48 content = loadFileContent("/api/v1/details-dummy/error-unknown-app-details.json");
49 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
52 } else if (path.startsWith("/api/v1/apps/") && path.endsWith("/download")) {
53 String appid = path.substring(13, path.length() - 9);
54 if (null != (content = loadFileContent("/api/v1/apps/" + appid + "-download.json"))) {
56 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
59 } else if (path.startsWith("/api/v1/apps/")) {
60 String appid = path.substring(13);
61 if (null != (content = loadFileContent("/api/v1/apps/" + appid + ".json"))) {
62 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
65 } else if (path.startsWith("/api/v1/developers") && path.endsWith("/products/") && session.getParameters().containsKey("only")) {
66 //usage: product details for a single product
67 if (null != (content = loadFileContent(path + session.getParameters().get("only").get(0) + ".json"))) {
68 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
71 } else if (path.equals("/api/v1/discover")) {
72 //usage: main store page
73 if (null != (content = loadFileContent("/api/v1/discover-data/discover.json"))) {
74 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
77 } else if (path.startsWith("/api/v1/discover/")) {
78 //usage: store category
79 if (null != (content = loadFileContent("/api/v1/discover-data/" + path.substring(17) + ".json"))) {
80 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
83 } else if (path.equals("/api/v1/gamers")) {
84 //usage: register a user
85 if (null != (content = loadFileContent("/api/v1/gamers/register-error.json"))) {
86 return newFixedLengthResponse(Response.Status.BAD_REQUEST, "application/json", content);
89 } else if (path.equals("/api/v1/gamers/me")) {
90 //usage: fetch user data
91 if (null != (content = loadFileContent("/api/v1/gamers/me.json"))) {
92 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
95 } else if (path.equals("/api/v1/gamers/key")) {
96 //usage: store gamer ouya public key via PUT
97 return newFixedLengthResponse(Response.Status.CREATED, null, "");
99 } else if (path.equals("/api/v1/search") && session.getParameters().containsKey("q")) {
100 //usage: search for games
101 String query = session.getParameters().get("q").get(0);
102 if (null != (content = loadFileContent("/api/v1/search-data/" + query.charAt(0) + ".json"))) {
103 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
106 } else if (!path.endsWith("/") && null != (content = loadFileContent(path))) {
107 //try if the path exists in the assets/stouyapi-www/ dir
108 return newFixedLengthResponse(Response.Status.OK, mimeTypeFromPath(path), content);
110 } else if (null != (content = loadFileContent(path.replaceAll("/$", "") + "/index.htm"))) {
111 //usage: /api/v1/developers/*/products
112 return newFixedLengthResponse(Response.Status.OK, "text/html", content);
114 } else if (!path.endsWith("/") && null != (content = loadAssetsFileContent(path))) {
115 //try if the path exists in the assets/ dir (without stouyapi-www)
116 return newFixedLengthResponse(Response.Status.OK, mimeTypeFromPath(path), content);
118 } else if (null != (content = loadAssetsFileContent(path.replaceAll("/$", "") + "/index.htm"))) {
119 //try if the path + index.htm exists in the assets/ dir (without stouyapi-www)
120 return newFixedLengthResponse(Response.Status.OK, "text/html", content);
122 } else if (path.startsWith("/api/v1/games/") && path.endsWith("/purchases")) {
123 //usage: purchases for non-existing game
124 if (null != (content = loadFileContent("/api/v1/games/purchases-empty.json"))) {
125 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
129 return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "File not found");
133 * Automatically determine the length of the input stream
135 protected Response newFixedLengthResponse(Response.IStatus status, String mimeType, InputStream data) {
138 length = streamLength(data);
140 } catch (IOException e) {
141 Log.e(TAG, e.getMessage());
142 return newFixedLengthResponse(
143 Response.Status.INTERNAL_ERROR, "text/plain",
144 "Error: " + e.getMessage()
147 return newFixedLengthResponse(status, mimeType, data, length);
150 protected InputStream loadFileContent(String path)
152 return loadAssetsFileContent(prefix + path);
155 protected InputStream loadAssetsFileContent(String path)
157 path = path.replaceAll("^/", "");
159 Log.d(TAG, "loadFileContent: " + path);
160 return assetManager.open(path, AssetManager.ACCESS_BUFFER);
161 } catch (IOException e) {
162 //file does not exist
163 Log.d(TAG, "loadFileContent: fail");
168 protected static int streamLength(InputStream inputStream) throws IOException {
169 byte[] buffer = new byte[4096];
170 int chunkBytesRead = 0;
172 while((chunkBytesRead = inputStream.read(buffer)) != -1) {
173 length += chunkBytesRead;
178 protected String mimeTypeFromPath(String path) {
179 if (path.endsWith(".htm")) {
181 } else if (path.endsWith(".ico")) {
182 return "image/vnd.microsoft.icon";
183 } else if (path.endsWith(".jpg")) {
185 } else if (path.endsWith(".png")) {
191 protected boolean isBinary(String path) {
192 return path.endsWith(".ico")
193 || path.endsWith(".jpg")
194 || path.endsWith(".png");