Permalink
Browse files

Android WebSocket: include cookies in request

Summary:
This PR updates #6851 from srikanthkh, fixing coding conventions and javadoc, and adding a test plan.

Added testing functions into the WebSocketExample page of the UIExplorer, including a tiny http server to set a cookie on demand. Instructions included in the UIExplorer app.
Closes #9114

Differential Revision: D4140534

Pulled By: lacker

fbshipit-source-id: e020ad0c6d1d3ea09c0c3564c1795b4e1bc4517d
  • Loading branch information...
1 parent a4bb4d2 commit be4afdde37ab6ff6ebe573821745887fd3edfb05 @antoinerousseau antoinerousseau committed with Facebook Github Bot Nov 7, 2016
@@ -32,10 +32,12 @@ const {
Text,
TextInput,
TouchableOpacity,
+ ScrollView,
View,
} = ReactNative;
const DEFAULT_WS_URL = 'ws://localhost:5555/';
+const DEFAULT_HTTP_URL = 'http://localhost:5556/';
const WS_EVENTS = [
'close',
'error',
@@ -95,6 +97,8 @@ function showValue(value) {
type State = {
url: string;
+ httpUrl: string;
+ fetchStatus: ?string;
socket: ?WebSocket;
socketState: ?number;
lastSocketEvent: ?string;
@@ -109,6 +113,8 @@ class WebSocketExample extends React.Component<any, any, State> {
state: State = {
url: DEFAULT_WS_URL,
+ httpUrl: DEFAULT_HTTP_URL,
+ fetchStatus: null,
socket: null,
socketState: null,
lastSocketEvent: null,
@@ -154,6 +160,19 @@ class WebSocketExample extends React.Component<any, any, State> {
this.setState({outgoingMessage: ''});
};
+ _sendHttp = () => {
+ this.setState({
+ fetchStatus: 'fetching',
+ });
+ fetch(this.state.httpUrl).then((response) => {
+ if (response.status >= 200 && response.status < 400) {
+ this.setState({
+ fetchStatus: 'OK',
+ });
+ }
+ });
+ };
+
_sendBinary = () => {
if (!this.state.socket ||
typeof ArrayBuffer === 'undefined' ||
@@ -176,16 +195,11 @@ class WebSocketExample extends React.Component<any, any, State> {
this.state.socket.readyState >= WebSocket.CLOSING;
const canSend = !!this.state.socket;
return (
- <View style={styles.container}>
+ <ScrollView style={styles.container}>
<View style={styles.note}>
- <Text>Pro tip:</Text>
+ <Text>To start the WS test server:</Text>
<Text style={styles.monospace}>
- node Examples/UIExplorer/websocket_test_server.js
- </Text>
- <Text>
- {' in the '}
- <Text style={styles.monospace}>react-native</Text>
- {' directory starts a test server.'}
+ ./Examples/UIExplorer/js/websocket_test_server.js
</Text>
</View>
<Row
@@ -207,16 +221,18 @@ class WebSocketExample extends React.Component<any, any, State> {
onChangeText={(url) => this.setState({url})}
value={this.state.url}
/>
- <Button
- onPress={this._connect}
- label="Connect"
- disabled={!canConnect}
- />
- <Button
- onPress={this._disconnect}
- label="Disconnect"
- disabled={canConnect}
- />
+ <View style={styles.buttonRow}>
+ <Button
+ onPress={this._connect}
+ label="Connect"
+ disabled={!canConnect}
+ />
+ <Button
+ onPress={this._disconnect}
+ label="Disconnect"
+ disabled={canConnect}
+ />
+ </View>
<TextInput
style={styles.textInput}
autoCorrect={false}
@@ -236,7 +252,32 @@ class WebSocketExample extends React.Component<any, any, State> {
disabled={!canSend}
/>
</View>
- </View>
+ <View style={styles.note}>
+ <Text>To start the HTTP test server:</Text>
+ <Text style={styles.monospace}>
+ ./Examples/UIExplorer/http_test_server.js
+ </Text>
+ </View>
+ <TextInput
+ style={styles.textInput}
+ autoCorrect={false}
+ placeholder="HTTP URL..."
+ onChangeText={(httpUrl) => this.setState({httpUrl})}
+ value={this.state.httpUrl}
+ />
+ <View style={styles.buttonRow}>
+ <Button
+ onPress={this._sendHttp}
+ label="Send HTTP request to set cookie"
+ disabled={this.state.fetchStatus === 'fetching'}
+ />
+ </View>
+ <View style={styles.note}>
+ <Text>
+ {this.state.fetchStatus === 'OK' ? 'Done. Check your WS server console to see if the next WS requests include the cookie (should be "wstest=OK")' : '-'}
+ </Text>
+ </View>
+ </ScrollView>
);
}
@@ -0,0 +1,43 @@
+#!/usr/bin/env node
+
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+/* eslint-env node */
+
+console.log(`\
+Test server for WebSocketExample
+
+This will set a cookie named "wstest" on the response of any incoming request.
+`);
+
+const connect = require('connect');
+const http = require('http');
+
+const app = connect();
+
+app.use(function(req, res) {
+ console.log('received request');
+ const cookieOptions = {
+ //httpOnly: true, // the cookie is not accessible by the user (javascript,...)
+ secure: false, // allow HTTP
+ };
+ res.cookie('wstest', 'OK', cookieOptions);
+ res.end('Cookie has been set!\n');
+});
+
+http.createServer(app).listen(5556);
@@ -1,3 +1,5 @@
+#!/usr/bin/env node
+
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
@@ -29,7 +31,7 @@ const WebSocket = require('ws');
console.log(`\
Test server for WebSocketExample
-This will send each incoming message right back to the other side.a
+This will send each incoming message right back to the other side.
Restart with the '--binary' command line flag to have it respond with an
ArrayBuffer instead of a string.
`);
@@ -39,6 +41,7 @@ const server = new WebSocket.Server({port: 5555});
server.on('connection', (ws) => {
ws.on('message', (message) => {
console.log('Received message:', message);
+ console.log('Cookie:', ws.upgradeReq.headers.cookie);
if (respondWithBinary) {
message = new Buffer(message);
}
@@ -14,6 +14,7 @@ android_library(
react_native_target('java/com/facebook/react/common:common'),
react_native_target('java/com/facebook/react/module/annotations:annotations'),
react_native_target('java/com/facebook/react/modules/core:core'),
+ react_native_target('java/com/facebook/react/modules/network:network'),
],
visibility = [
'PUBLIC',
@@ -29,6 +29,7 @@
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.facebook.react.modules.network.ForwardingCookieHandler;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -43,6 +44,7 @@
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import okio.Buffer;
@@ -54,10 +56,12 @@
private final Map<Integer, WebSocket> mWebSocketConnections = new HashMap<>();
private ReactContext mReactContext;
+ private ForwardingCookieHandler mCookieHandler;
public WebSocketModule(ReactApplicationContext context) {
super(context);
mReactContext = context;
+ mCookieHandler = new ForwardingCookieHandler(context);
}
private void sendEvent(String eventName, WritableMap params) {
@@ -87,11 +91,16 @@ public void connect(
.tag(id)
.url(url);
+ String cookie = getCookie(url);
+ if (cookie != null) {
+ builder.addHeader("Cookie", cookie);
+ }
+
if (headers != null) {
ReadableMapKeySetIterator iterator = headers.keySetIterator();
if (!headers.hasKey("origin")) {
- builder.addHeader("origin", setDefaultOrigin(url));
+ builder.addHeader("origin", getDefaultOrigin(url));
}
while (iterator.hasNextKey()) {
@@ -105,7 +114,7 @@ public void connect(
}
}
} else {
- builder.addHeader("origin", setDefaultOrigin(url));
+ builder.addHeader("origin", getDefaultOrigin(url));
}
if (protocols != null && protocols.size() > 0) {
@@ -256,13 +265,13 @@ private void notifyWebSocketFailed(int id, String message) {
}
/**
- * Set a default origin
+ * Get the default HTTP(S) origin for a specific WebSocket URI
*
- * @param Websocket connection endpoint
- * @return A string of the endpoint converted to HTTP protocol
+ * @param String uri
+ * @return A string of the endpoint converted to HTTP protocol (http[s]://host[:port])
*/
- private static String setDefaultOrigin(String uri) {
+ private static String getDefaultOrigin(String uri) {
try {
String defaultOrigin;
String scheme = "";
@@ -285,8 +294,31 @@ private static String setDefaultOrigin(String uri) {
}
return defaultOrigin;
+
} catch (URISyntaxException e) {
- throw new IllegalArgumentException("Unable to set " + uri + " as default origin header.");
+ throw new IllegalArgumentException("Unable to set " + uri + " as default origin header");
+ }
+ }
+
+ /**
+ * Get the cookie for a specific domain
+ *
+ * @param String uri
+ * @return The cookie header or null if none is set
+ */
+ private String getCookie(String uri) {
+ try {
+ URI origin = new URI(getDefaultOrigin(uri));
+ Map<String, List<String>> cookieMap = mCookieHandler.get(origin, new HashMap());
+ List<String> cookieList = cookieMap.get("Cookie");
+
+ if (cookieList == null || cookieList.isEmpty()) {
+ return null;
+ }
+
+ return cookieList.get(0);
+ } catch (URISyntaxException | IOException e) {
+ throw new IllegalArgumentException("Unable to get cookie from " + uri);
}
}
}

0 comments on commit be4afdd

Please sign in to comment.