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...
antoinerousseau authored and Facebook Github Bot committed Nov 7, 2016
1 parent a4bb4d2 commit be4afdde37ab6ff6ebe573821745887fd3edfb05
@@ -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.