Commit aae9d54f authored by Jeffrey I. Schiller's avatar Jeffrey I. Schiller Committed by Gerrit Review System

Add RendezvousServlet to App Inventor Server. This is used to help the

Blocks Editor find the associated phone for debugging over wireless.

Change-Id: I8f825746b4d2df2a31ce6f6898bdc2009280ddd3
parent 36c393b2
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt
/*
* RendezvousServlet -- This servlet acts as the rendezvous point
* between the blocks editor and a phone being used for debugging
* with a WiFi connection. This was originally written in Python
* using the "Bottle" micro-framework. Here is that code:
*
* #!/usr/bin/python
* from bottle import run,route,app,request,response,template,default_app,Bottle,debug,abort
* from flup.server.fcgi import WSGIServer
* from cStringIO import StringIO
* import memcache
*
* app = Bottle()
* default_app.push(app)
*
* @route('/', method='POST')
* def store():
* c = memcache.Client(['127.0.0.1:11211',])
* key = request.POST.get('key')
* if not key:
* abort(404, 'No Key Specified')
* d = {}
* for k,v in request.POST.items():
* d[k] = v
* c.set('rr-%s' % key, d, 1800)
* return d
*
* @route('/<key>')
* def fetch(key):
* c = memcache.Client(['127.0.0.1:11211',])
* return c.get('rr-%s' % key)
*
* debug(True)
*
* ##run(host='127.0.0.1', port=8080)
* WSGIServer(app).run()
*
* # End of Python Code
*
* This code is a little bit more complicated. In part because it
* is written in Java and it is intended to be run within the
* Google App Engine. The App Engine can sometimes disable
* memcache, which this code uses both for speed and to avoid
* using the datastore for data which is valuable for typically
* 10 to 15 seconds!
*
* When memcache is disabled we use the datastore. However when
* memcache is available we do *NOT* use the datastore at
* all. This is a little different from the way most code uses
* memcache, literally as a cache in front of a real data
* store. Again, this is driven by the desire for speed and the
* short life of the data itself.
*
* Note: At the moment there is no code to cleaup entries left in
* the datastore. However each entry is marked with a used date,
* so it is pretty easy to write code at a later date to remove
* stale entries. Where stale can be defined to be data that is
* more then a few minutes old!
*
*/
package com.google.appinventor.server;
import com.google.appengine.api.capabilities.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.HashMap;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.memcache.Expiration;
import org.json.JSONObject;
import org.json.JSONException;
import com.google.appinventor.server.storage.StorageIo;
import com.google.appinventor.server.storage.StorageIoInstanceHolder;
@SuppressWarnings("unchecked")
public class RendezvousServlet extends HttpServlet {
private final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
private final String rendezvousuuid = "c96d8ac6-e571-48bb-9e1f-58df18574e43"; // UUID Generated by JIS
private final StorageIo storageIo = StorageIoInstanceHolder.INSTANCE;
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String uriComponents[] = req.getRequestURI().split("/", 5);
String key = uriComponents[uriComponents.length-1];
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
JSONObject jsonObject = new JSONObject();
if (memcacheNotAvailable()) {
// Don't have memcache at the moment, use the data store.
String ipAddress = storageIo.findIpAddressByKey(key);
if (ipAddress == null) {
// out.println("");
} else {
try {
jsonObject.put("key", key);
jsonObject.put("ipaddr", ipAddress);
} catch (JSONException e) {
e.printStackTrace();
}
out.println(jsonObject.toString());
}
return;
}
Object value = memcache.get(rendezvousuuid + key);
if (value == null) {
// out.println("");
return;
}
if (value instanceof Map) {
Map map = (Map<String, String>) value;
for (Object mkey : map.keySet()) {
try {
jsonObject.put((String) mkey, map.get(mkey));
} catch (JSONException e) {
e.printStackTrace();
}
}
out.println(jsonObject.toString());
} else
out.println("");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
PrintWriter out = resp.getWriter();
BufferedReader input = new BufferedReader(new InputStreamReader(req.getInputStream()));
String queryString = input.readLine();
if (queryString == null) {
out.println("queryString is null");
return;
}
HashMap<String, String> params = getQueryMap(queryString);
String key = params.get("key");
if (key == null) {
out.println("no key");
return;
}
if (memcacheNotAvailable()) {
String ipAddress = params.get("ipaddr");
if (ipAddress == null) { // Malformed request
out.println("no ipaddress");
return;
}
storageIo.storeIpAddressByKey(key, ipAddress);
out.println("OK (Datastore)");
return;
}
memcache.put(rendezvousuuid + key, params, Expiration.byDeltaSeconds(300));
out.println("OK");
}
public void destroy() {
super.destroy();
}
private static HashMap<String, String> getQueryMap(String query) {
String[] params = query.split("&");
HashMap<String, String> map = new HashMap<String, String>();
for (String param : params) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}
return map;
}
private boolean memcacheNotAvailable() {
CapabilitiesService service = CapabilitiesServiceFactory.getCapabilitiesService();
CapabilityStatus status = service.getStatus(Capability.MEMCACHE).getStatus();
if (status == CapabilityStatus.DISABLED) {
return true;
} else {
return false;
}
}
}
......@@ -21,6 +21,7 @@ import com.google.appinventor.server.storage.StoredData.ProjectData;
import com.google.appinventor.server.storage.StoredData.UserData;
import com.google.appinventor.server.storage.StoredData.UserFileData;
import com.google.appinventor.server.storage.StoredData.UserProjectData;
import com.google.appinventor.server.storage.StoredData.RendezvousData;
import com.google.appinventor.shared.rpc.Motd;
import com.google.appinventor.shared.rpc.project.Project;
import com.google.appinventor.shared.rpc.project.ProjectSourceZip;
......@@ -109,6 +110,7 @@ public class ObjectifyStorageIo implements StorageIo {
ObjectifyService.register(FileData.class);
ObjectifyService.register(UserFileData.class);
ObjectifyService.register(MotdData.class);
ObjectifyService.register(RendezvousData.class);
}
ObjectifyStorageIo() {
......@@ -1370,6 +1372,46 @@ public class ObjectifyStorageIo implements StorageIo {
return userData.id;
}
@Override
public String findIpAddressByKey(final String key) {
Objectify datastore = ObjectifyService.begin();
RendezvousData data = datastore.query(RendezvousData.class).filter("key", key).get();
if (data == null) {
return null;
} else {
return data.ipAddress;
}
}
@Override
public void storeIpAddressByKey(final String key, final String ipAddress) {
Objectify datastore = ObjectifyService.begin();
final RendezvousData data = datastore.query(RendezvousData.class).filter("key", key).get();
try {
runJobWithRetries(new JobRetryHelper() {
@Override
public void run(Objectify datastore) {
RendezvousData new_data = null;
if (data == null) {
new_data = new RendezvousData();
new_data.id = null;
new_data.key = key;
new_data.ipAddress = ipAddress;
new_data.used = new Date(); // So we can cleanup old entries
datastore.put(new_data);
} else {
new_data = data;
new_data.ipAddress = ipAddress;
new_data.used = new Date();
datastore.put(new_data);
}
}
});
} catch (ObjectifyException e) {
throw CrashReport.createAndLogError(LOG, null, null, e);
}
}
private void initMotd() {
try {
runJobWithRetries(new JobRetryHelper() {
......
......@@ -393,4 +393,28 @@ public interface StorageIo {
*/
String findUserByEmail(String email) throws NoSuchElementException;
/**
* Find a phone's IP address given the six character key. Used by the
* RendezvousServlet. This is used only when memcache is unavailable.
*
* @param key the six character key
* @return Ip Address as string or null if not found
*
*/
String findIpAddressByKey(String key);
/**
* Store a phone's IP address indexed by six character key. Used by the
* RendezvousServlet. This is used only when memcache is unavailable.
*
* Note: Nothing currently cleans up these entries, but we have a
* timestamp field which we update so a later process can recognize
* and remove stale entries.
*
* @param key the six character key
* @param ipAddress the IP Address of the phone
*
*/
void storeIpAddressByKey(String key, String ipAddress);
}
......@@ -166,4 +166,20 @@ public class StoredData {
// More MOTD detail, if any
String content;
}
// Rendezvous Data -- Only used when memcache is unavailable
@Unindexed
static final class RendezvousData {
@Id Long id;
// Six character key entered by user (or scanned).
@Indexed public String key;
// Ip Address of phone
public String ipAddress;
public Date used; // Used during (manual) cleanup to determine if this entry can be pruned
}
}
......@@ -52,6 +52,7 @@
<url-pattern>/learn/*</url-pattern>
<url-pattern>/about/*</url-pattern>
<url-pattern>/forum/*</url-pattern>
<url-pattern>/rendezvous/*</url-pattern>
</web-resource-collection>
</security-constraint>
......@@ -67,6 +68,16 @@
<!-- Servlets -->
<!-- rendezvious -->
<servlet>
<servlet-name>rendezvousServlet</servlet-name>
<servlet-class>com.google.appinventor.server.RendezvousServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>rendezvousServlet</servlet-name>
<url-pattern>/rendezvous/*</url-pattern>
</servlet-mapping>
<!-- download -->
<servlet>
<servlet-name>downloadServlet</servlet-name>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment