-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
An initial commit for Firebase Event Proxy (#236)
* initial commit of Firebase Event Proxy * Add licenses at the beginning of all files * Add whitespace and more comments
- Loading branch information
1 parent
94c3b29
commit 61b722a
Showing
11 changed files
with
540 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# App Engine Firebase Event Proxy | ||
|
||
An example app that illustrates how to create a Java App Engine Standard Environment | ||
app that proxies Firebase events to another App Engine app. | ||
|
||
# Java Firebase Event Proxy | ||
Illustrates how to authenticate and subscribe to Firebase from Java App Engine. | ||
|
||
# Python App Engine Listener | ||
Illustrates how to authenticate messages received from the proxy app. | ||
|
||
## Setup | ||
|
||
### Java Firebase Event Proxy | ||
Firebase Secret | ||
Put your Firebase secret in the file: | ||
gae-firebase-event-proxy/src/main/webapp/firebase-secret.properties | ||
``` | ||
firebaseSecret=<Your Firebase secret> | ||
``` | ||
|
||
* Billing must be enabled from Cloud console. | ||
* Manual scaling should turned on and configured to 1 instance in appengine-web.xml | ||
|
||
## Running locally | ||
### Java Firebase Event Proxy | ||
``` | ||
cd gae-firebase-event-proxy | ||
mvn appengine:devserver | ||
``` | ||
|
||
### Python App Engine Listener | ||
``` | ||
cd gae-firebase-listener-python | ||
dev_appserver . | ||
``` | ||
|
||
## Deploying | ||
|
||
### Java Firebase Event Proxy | ||
``` | ||
cd gae-firebase-event-proxy | ||
mvn appengine:upload | ||
``` | ||
|
||
### Python App Engine Listener | ||
``` | ||
appcfg.py -A <your app id> -V v1 update gae-firebase-listener-python | ||
``` |
156 changes: 156 additions & 0 deletions
156
appengine/firebase-event-proxy/gae-firebase-event-proxy/pom.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
<!-- | ||
Copyright 2015 Google Inc. All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
--> | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<packaging>war</packaging> | ||
<version>1.0-SNAPSHOT</version> | ||
|
||
<groupId>com.example.GaeFirebaseEventProxy</groupId> | ||
<artifactId>GaeFirebaseEventProxy</artifactId> | ||
|
||
<properties> | ||
<app.id>gae-firebase-event-proxy</app.id> | ||
<app.version>1</app.version> | ||
<appengine.version>1.9.36</appengine.version> | ||
<gcloud.plugin.version>2.0.9.74.v20150814</gcloud.plugin.version> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation> | ||
</properties> | ||
|
||
<prerequisites> | ||
<maven>3.1.0</maven> | ||
</prerequisites> | ||
|
||
<dependencies> | ||
<!-- Compile/runtime dependencies --> | ||
<dependency> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>appengine-api-1.0-sdk</artifactId> | ||
<version>${appengine.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.servlet</groupId> | ||
<artifactId>servlet-api</artifactId> | ||
<version>2.5</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>jstl</groupId> | ||
<artifactId>jstl</artifactId> | ||
<version>1.2</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.firebase</groupId> | ||
<artifactId>firebase-client-jvm</artifactId> | ||
<version>[1.0.8,)</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-core</artifactId> | ||
<version>2.7.3</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.firebase</groupId> | ||
<artifactId>firebase-token-generator</artifactId> | ||
<version>2.0.0</version> | ||
</dependency> | ||
|
||
<!-- Test Dependencies --> | ||
<dependency> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>appengine-testing</artifactId> | ||
<version>${appengine.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>appengine-api-stubs</artifactId> | ||
<version>${appengine.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<!-- for hot reload of the web application--> | ||
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.codehaus.mojo</groupId> | ||
<artifactId>versions-maven-plugin</artifactId> | ||
<version>2.1</version> | ||
<executions> | ||
<execution> | ||
<phase>compile</phase> | ||
<goals> | ||
<goal>display-dependency-updates</goal> | ||
<goal>display-plugin-updates</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<version>3.1</version> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.7</source> | ||
<target>1.7</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-war-plugin</artifactId> | ||
<version>2.4</version> | ||
<configuration> | ||
<archiveClasses>true</archiveClasses> | ||
<webResources> | ||
<!-- in order to interpolate version from pom into appengine-web.xml --> | ||
<resource> | ||
<directory>${basedir}/src/main/webapp/WEB-INF</directory> | ||
<filtering>true</filtering> | ||
<targetPath>WEB-INF</targetPath> | ||
</resource> | ||
</webResources> | ||
</configuration> | ||
</plugin> | ||
|
||
<plugin> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>appengine-maven-plugin</artifactId> | ||
<version>${appengine.version}</version> | ||
<configuration> | ||
<enableJarClasses>false</enableJarClasses> | ||
<version>${app.version}</version> | ||
<!-- Comment in the below snippet to bind to all IPs instead of just localhost --> | ||
<!-- address>0.0.0.0</address> | ||
<port>8080</port --> | ||
<!-- Comment in the below snippet to enable local debugging with a remote debugger | ||
like those included with Eclipse or IntelliJ --> | ||
<!-- jvmFlags> | ||
<jvmFlag>-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n</jvmFlag> | ||
</jvmFlags --> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>gcloud-maven-plugin</artifactId> | ||
<version>${gcloud.plugin.version}</version> | ||
<configuration> | ||
<set_default>true</set_default> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
144 changes: 144 additions & 0 deletions
144
...ebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/FirebaseEventProxy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright 2016 Google Inc. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.GaeFirebaseEventProxy; | ||
|
||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.HttpURLConnection; | ||
import java.net.URL; | ||
import java.net.URLEncoder; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Properties; | ||
import java.util.logging.Logger; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.firebase.client.AuthData; | ||
import com.firebase.client.DataSnapshot; | ||
import com.firebase.client.Firebase; | ||
import com.firebase.client.FirebaseError; | ||
import com.firebase.client.ValueEventListener; | ||
import com.firebase.security.token.TokenGenerator; | ||
import com.google.appengine.api.utils.SystemProperty; | ||
|
||
public class FirebaseEventProxy { | ||
|
||
private static final Logger log = Logger.getLogger(FirebaseEventProxy.class.getName()); | ||
|
||
private String firebaseAuthToken; | ||
|
||
public FirebaseEventProxy() { | ||
// Store Firebase authentication token as an instance variable. | ||
this.firebaseAuthToken = this.getFirebaseAuthToken(this.getFirebaseSecret()); | ||
} | ||
|
||
public void start() { | ||
String FIREBASE_LOCATION = "https://gae-fb-proxy.firebaseio.com/"; | ||
Firebase firebase = new Firebase(FIREBASE_LOCATION); | ||
|
||
// Authenticate with Firebase | ||
firebase.authWithCustomToken(this.firebaseAuthToken, new Firebase.AuthResultHandler() { | ||
@Override | ||
public void onAuthenticationError(FirebaseError error) { | ||
log.severe("Firebase login error: " + error.getMessage()); | ||
} | ||
|
||
@Override | ||
public void onAuthenticated(AuthData auth) { | ||
log.info("Firebase login successful"); | ||
} | ||
}); | ||
|
||
// Subscribe to value events. Depending on use case, you may want to subscribe to child events | ||
// through childEventListener. | ||
firebase.addValueEventListener(new ValueEventListener() { | ||
@Override | ||
public void onDataChange(DataSnapshot snapshot) { | ||
if (snapshot.exists()) { | ||
try { | ||
// Convert value to JSON using Jackson | ||
String json = new ObjectMapper().writeValueAsString(snapshot.getValue()); | ||
|
||
// Replace the URL with the url of your own listener app. | ||
URL dest = new URL("http://gae-firebase-listener-python.appspot.com/log"); | ||
HttpURLConnection connection = (HttpURLConnection) dest.openConnection(); | ||
connection.setRequestMethod("POST"); | ||
connection.setDoOutput(true); | ||
|
||
// Rely on X-Appengine-Inbound-Appid to authenticate. Turning off redirects is | ||
// required to enable. | ||
connection.setInstanceFollowRedirects(false); | ||
|
||
// Fill out header if in dev environment | ||
if (SystemProperty.environment.value() != SystemProperty.Environment.Value.Production) { | ||
connection.setRequestProperty("X-Appengine-Inbound-Appid", "dev-instance"); | ||
} | ||
|
||
// Put Firebase data into http request | ||
StringBuilder stringBuilder = new StringBuilder(); | ||
stringBuilder.append("&fbSnapshot="); | ||
stringBuilder.append(URLEncoder.encode(json, "UTF-8")); | ||
connection.getOutputStream().write(stringBuilder.toString().getBytes()); | ||
if (connection.getResponseCode() != 200) { | ||
log.severe("Forwarding failed"); | ||
} else { | ||
log.info("Sent: " + json); | ||
} | ||
} catch (JsonProcessingException e) { | ||
log.severe("Unable to convert Firebase response to JSON: " + e.getMessage()); | ||
} catch (IOException e) { | ||
log.severe("Error in connecting to app engine: " + e.getMessage()); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void onCancelled(FirebaseError error) { | ||
log.severe("Firebase connection cancelled: " + error.getMessage()); | ||
} | ||
}); | ||
} | ||
|
||
private String getFirebaseSecret() { | ||
Properties props = new Properties(); | ||
try { | ||
// Read from src/main/webapp/firebase-secrets.properties | ||
InputStream inputStream = new FileInputStream("firebase-secret.properties"); | ||
props.load(inputStream); | ||
return props.getProperty("firebaseSecret"); | ||
} catch (java.net.MalformedURLException e) { | ||
throw new RuntimeException( | ||
"Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: " | ||
+ e.getMessage()); | ||
} catch (IOException e) { | ||
throw new RuntimeException( | ||
"Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: " | ||
+ e.getMessage()); | ||
} | ||
} | ||
|
||
private String getFirebaseAuthToken(String firebaseSecret) { | ||
Map<String, Object> authPayload = new HashMap<String, Object>(); | ||
// uid and provider will have to match what you have in your firebase security rules | ||
authPayload.put("uid", "gae-firebase-event-proxy"); | ||
authPayload.put("provider", "com.example"); | ||
TokenGenerator tokenGenerator = new TokenGenerator(firebaseSecret); | ||
return tokenGenerator.createToken(authPayload); | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
...ent-proxy/src/main/java/com/example/GaeFirebaseEventProxy/ServletContextListenerImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright 2016 Google Inc. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.GaeFirebaseEventProxy; | ||
|
||
import java.util.logging.Logger; | ||
|
||
import javax.servlet.ServletContextEvent; | ||
import javax.servlet.ServletContextListener; | ||
|
||
// ServletContextListener that is called whenever your App Engine app starts up. | ||
public class ServletContextListenerImpl implements ServletContextListener { | ||
|
||
private static final Logger log = Logger.getLogger(ServletContextListener.class.getName()); | ||
|
||
@Override | ||
public void contextInitialized(ServletContextEvent event) { | ||
log.info("Starting ...."); | ||
FirebaseEventProxy proxy = new FirebaseEventProxy(); | ||
proxy.start(); | ||
} | ||
|
||
@Override | ||
public void contextDestroyed(ServletContextEvent event) { | ||
// App Engine does not currently invoke this method. | ||
} | ||
} |
Oops, something went wrong.