Skip to content

Commit 61b722a

Browse files
misterwilliamlesv
authored andcommitted
An initial commit for Firebase Event Proxy (GoogleCloudPlatform#236)
* initial commit of Firebase Event Proxy * Add licenses at the beginning of all files * Add whitespace and more comments
1 parent 94c3b29 commit 61b722a

File tree

11 files changed

+540
-0
lines changed

11 files changed

+540
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# App Engine Firebase Event Proxy
2+
3+
An example app that illustrates how to create a Java App Engine Standard Environment
4+
app that proxies Firebase events to another App Engine app.
5+
6+
# Java Firebase Event Proxy
7+
Illustrates how to authenticate and subscribe to Firebase from Java App Engine.
8+
9+
# Python App Engine Listener
10+
Illustrates how to authenticate messages received from the proxy app.
11+
12+
## Setup
13+
14+
### Java Firebase Event Proxy
15+
Firebase Secret
16+
Put your Firebase secret in the file:
17+
gae-firebase-event-proxy/src/main/webapp/firebase-secret.properties
18+
```
19+
firebaseSecret=<Your Firebase secret>
20+
```
21+
22+
* Billing must be enabled from Cloud console.
23+
* Manual scaling should turned on and configured to 1 instance in appengine-web.xml
24+
25+
## Running locally
26+
### Java Firebase Event Proxy
27+
```
28+
cd gae-firebase-event-proxy
29+
mvn appengine:devserver
30+
```
31+
32+
### Python App Engine Listener
33+
```
34+
cd gae-firebase-listener-python
35+
dev_appserver .
36+
```
37+
38+
## Deploying
39+
40+
### Java Firebase Event Proxy
41+
```
42+
cd gae-firebase-event-proxy
43+
mvn appengine:upload
44+
```
45+
46+
### Python App Engine Listener
47+
```
48+
appcfg.py -A <your app id> -V v1 update gae-firebase-listener-python
49+
```
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<!--
2+
Copyright 2015 Google Inc. All Rights Reserved.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
-->
13+
<?xml version="1.0" encoding="UTF-8"?>
14+
<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">
15+
16+
<modelVersion>4.0.0</modelVersion>
17+
<packaging>war</packaging>
18+
<version>1.0-SNAPSHOT</version>
19+
20+
<groupId>com.example.GaeFirebaseEventProxy</groupId>
21+
<artifactId>GaeFirebaseEventProxy</artifactId>
22+
23+
<properties>
24+
<app.id>gae-firebase-event-proxy</app.id>
25+
<app.version>1</app.version>
26+
<appengine.version>1.9.36</appengine.version>
27+
<gcloud.plugin.version>2.0.9.74.v20150814</gcloud.plugin.version>
28+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
29+
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
30+
</properties>
31+
32+
<prerequisites>
33+
<maven>3.1.0</maven>
34+
</prerequisites>
35+
36+
<dependencies>
37+
<!-- Compile/runtime dependencies -->
38+
<dependency>
39+
<groupId>com.google.appengine</groupId>
40+
<artifactId>appengine-api-1.0-sdk</artifactId>
41+
<version>${appengine.version}</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>javax.servlet</groupId>
45+
<artifactId>servlet-api</artifactId>
46+
<version>2.5</version>
47+
<scope>provided</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>jstl</groupId>
51+
<artifactId>jstl</artifactId>
52+
<version>1.2</version>
53+
</dependency>
54+
<dependency>
55+
<groupId>com.firebase</groupId>
56+
<artifactId>firebase-client-jvm</artifactId>
57+
<version>[1.0.8,)</version>
58+
</dependency>
59+
<dependency>
60+
<groupId>com.fasterxml.jackson.core</groupId>
61+
<artifactId>jackson-core</artifactId>
62+
<version>2.7.3</version>
63+
</dependency>
64+
<dependency>
65+
<groupId>com.firebase</groupId>
66+
<artifactId>firebase-token-generator</artifactId>
67+
<version>2.0.0</version>
68+
</dependency>
69+
70+
<!-- Test Dependencies -->
71+
<dependency>
72+
<groupId>com.google.appengine</groupId>
73+
<artifactId>appengine-testing</artifactId>
74+
<version>${appengine.version}</version>
75+
<scope>test</scope>
76+
</dependency>
77+
<dependency>
78+
<groupId>com.google.appengine</groupId>
79+
<artifactId>appengine-api-stubs</artifactId>
80+
<version>${appengine.version}</version>
81+
<scope>test</scope>
82+
</dependency>
83+
</dependencies>
84+
85+
<build>
86+
<!-- for hot reload of the web application-->
87+
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
88+
<plugins>
89+
<plugin>
90+
<groupId>org.codehaus.mojo</groupId>
91+
<artifactId>versions-maven-plugin</artifactId>
92+
<version>2.1</version>
93+
<executions>
94+
<execution>
95+
<phase>compile</phase>
96+
<goals>
97+
<goal>display-dependency-updates</goal>
98+
<goal>display-plugin-updates</goal>
99+
</goals>
100+
</execution>
101+
</executions>
102+
</plugin>
103+
<plugin>
104+
<groupId>org.apache.maven.plugins</groupId>
105+
<version>3.1</version>
106+
<artifactId>maven-compiler-plugin</artifactId>
107+
<configuration>
108+
<source>1.7</source>
109+
<target>1.7</target>
110+
</configuration>
111+
</plugin>
112+
<plugin>
113+
<groupId>org.apache.maven.plugins</groupId>
114+
<artifactId>maven-war-plugin</artifactId>
115+
<version>2.4</version>
116+
<configuration>
117+
<archiveClasses>true</archiveClasses>
118+
<webResources>
119+
<!-- in order to interpolate version from pom into appengine-web.xml -->
120+
<resource>
121+
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
122+
<filtering>true</filtering>
123+
<targetPath>WEB-INF</targetPath>
124+
</resource>
125+
</webResources>
126+
</configuration>
127+
</plugin>
128+
129+
<plugin>
130+
<groupId>com.google.appengine</groupId>
131+
<artifactId>appengine-maven-plugin</artifactId>
132+
<version>${appengine.version}</version>
133+
<configuration>
134+
<enableJarClasses>false</enableJarClasses>
135+
<version>${app.version}</version>
136+
<!-- Comment in the below snippet to bind to all IPs instead of just localhost -->
137+
<!-- address>0.0.0.0</address>
138+
<port>8080</port -->
139+
<!-- Comment in the below snippet to enable local debugging with a remote debugger
140+
like those included with Eclipse or IntelliJ -->
141+
<!-- jvmFlags>
142+
<jvmFlag>-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n</jvmFlag>
143+
</jvmFlags -->
144+
</configuration>
145+
</plugin>
146+
<plugin>
147+
<groupId>com.google.appengine</groupId>
148+
<artifactId>gcloud-maven-plugin</artifactId>
149+
<version>${gcloud.plugin.version}</version>
150+
<configuration>
151+
<set_default>true</set_default>
152+
</configuration>
153+
</plugin>
154+
</plugins>
155+
</build>
156+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.GaeFirebaseEventProxy;
18+
19+
import java.io.FileInputStream;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.net.HttpURLConnection;
23+
import java.net.URL;
24+
import java.net.URLEncoder;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Properties;
28+
import java.util.logging.Logger;
29+
30+
import com.fasterxml.jackson.core.JsonProcessingException;
31+
import com.fasterxml.jackson.databind.ObjectMapper;
32+
import com.firebase.client.AuthData;
33+
import com.firebase.client.DataSnapshot;
34+
import com.firebase.client.Firebase;
35+
import com.firebase.client.FirebaseError;
36+
import com.firebase.client.ValueEventListener;
37+
import com.firebase.security.token.TokenGenerator;
38+
import com.google.appengine.api.utils.SystemProperty;
39+
40+
public class FirebaseEventProxy {
41+
42+
private static final Logger log = Logger.getLogger(FirebaseEventProxy.class.getName());
43+
44+
private String firebaseAuthToken;
45+
46+
public FirebaseEventProxy() {
47+
// Store Firebase authentication token as an instance variable.
48+
this.firebaseAuthToken = this.getFirebaseAuthToken(this.getFirebaseSecret());
49+
}
50+
51+
public void start() {
52+
String FIREBASE_LOCATION = "https://gae-fb-proxy.firebaseio.com/";
53+
Firebase firebase = new Firebase(FIREBASE_LOCATION);
54+
55+
// Authenticate with Firebase
56+
firebase.authWithCustomToken(this.firebaseAuthToken, new Firebase.AuthResultHandler() {
57+
@Override
58+
public void onAuthenticationError(FirebaseError error) {
59+
log.severe("Firebase login error: " + error.getMessage());
60+
}
61+
62+
@Override
63+
public void onAuthenticated(AuthData auth) {
64+
log.info("Firebase login successful");
65+
}
66+
});
67+
68+
// Subscribe to value events. Depending on use case, you may want to subscribe to child events
69+
// through childEventListener.
70+
firebase.addValueEventListener(new ValueEventListener() {
71+
@Override
72+
public void onDataChange(DataSnapshot snapshot) {
73+
if (snapshot.exists()) {
74+
try {
75+
// Convert value to JSON using Jackson
76+
String json = new ObjectMapper().writeValueAsString(snapshot.getValue());
77+
78+
// Replace the URL with the url of your own listener app.
79+
URL dest = new URL("http://gae-firebase-listener-python.appspot.com/log");
80+
HttpURLConnection connection = (HttpURLConnection) dest.openConnection();
81+
connection.setRequestMethod("POST");
82+
connection.setDoOutput(true);
83+
84+
// Rely on X-Appengine-Inbound-Appid to authenticate. Turning off redirects is
85+
// required to enable.
86+
connection.setInstanceFollowRedirects(false);
87+
88+
// Fill out header if in dev environment
89+
if (SystemProperty.environment.value() != SystemProperty.Environment.Value.Production) {
90+
connection.setRequestProperty("X-Appengine-Inbound-Appid", "dev-instance");
91+
}
92+
93+
// Put Firebase data into http request
94+
StringBuilder stringBuilder = new StringBuilder();
95+
stringBuilder.append("&fbSnapshot=");
96+
stringBuilder.append(URLEncoder.encode(json, "UTF-8"));
97+
connection.getOutputStream().write(stringBuilder.toString().getBytes());
98+
if (connection.getResponseCode() != 200) {
99+
log.severe("Forwarding failed");
100+
} else {
101+
log.info("Sent: " + json);
102+
}
103+
} catch (JsonProcessingException e) {
104+
log.severe("Unable to convert Firebase response to JSON: " + e.getMessage());
105+
} catch (IOException e) {
106+
log.severe("Error in connecting to app engine: " + e.getMessage());
107+
}
108+
}
109+
}
110+
111+
@Override
112+
public void onCancelled(FirebaseError error) {
113+
log.severe("Firebase connection cancelled: " + error.getMessage());
114+
}
115+
});
116+
}
117+
118+
private String getFirebaseSecret() {
119+
Properties props = new Properties();
120+
try {
121+
// Read from src/main/webapp/firebase-secrets.properties
122+
InputStream inputStream = new FileInputStream("firebase-secret.properties");
123+
props.load(inputStream);
124+
return props.getProperty("firebaseSecret");
125+
} catch (java.net.MalformedURLException e) {
126+
throw new RuntimeException(
127+
"Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: "
128+
+ e.getMessage());
129+
} catch (IOException e) {
130+
throw new RuntimeException(
131+
"Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: "
132+
+ e.getMessage());
133+
}
134+
}
135+
136+
private String getFirebaseAuthToken(String firebaseSecret) {
137+
Map<String, Object> authPayload = new HashMap<String, Object>();
138+
// uid and provider will have to match what you have in your firebase security rules
139+
authPayload.put("uid", "gae-firebase-event-proxy");
140+
authPayload.put("provider", "com.example");
141+
TokenGenerator tokenGenerator = new TokenGenerator(firebaseSecret);
142+
return tokenGenerator.createToken(authPayload);
143+
}
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.GaeFirebaseEventProxy;
18+
19+
import java.util.logging.Logger;
20+
21+
import javax.servlet.ServletContextEvent;
22+
import javax.servlet.ServletContextListener;
23+
24+
// ServletContextListener that is called whenever your App Engine app starts up.
25+
public class ServletContextListenerImpl implements ServletContextListener {
26+
27+
private static final Logger log = Logger.getLogger(ServletContextListener.class.getName());
28+
29+
@Override
30+
public void contextInitialized(ServletContextEvent event) {
31+
log.info("Starting ....");
32+
FirebaseEventProxy proxy = new FirebaseEventProxy();
33+
proxy.start();
34+
}
35+
36+
@Override
37+
public void contextDestroyed(ServletContextEvent event) {
38+
// App Engine does not currently invoke this method.
39+
}
40+
}

0 commit comments

Comments
 (0)