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 https://github.com/facebook/react-native/pull/9114

Differential Revision: D4140534

Pulled By: lacker

fbshipit-source-id: e020ad0c6d1d3ea09c0c3564c1795b4e1bc4517d
This commit is contained in:
Antoine Rousseau 2016-11-07 10:37:11 -08:00 committed by Facebook Github Bot
parent a4bb4d25f5
commit be4afdde37
5 changed files with 147 additions and 27 deletions

View File

@ -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>
);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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',

View File

@ -29,6 +29,7 @@ import com.facebook.react.bridge.WritableMap;
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.URISyntaxException;
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 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
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 class WebSocketModule extends ReactContextBaseJavaModule {
.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 class WebSocketModule extends ReactContextBaseJavaModule {
}
}
} else {
builder.addHeader("origin", setDefaultOrigin(url));
builder.addHeader("origin", getDefaultOrigin(url));
}
if (protocols != null && protocols.size() > 0) {
@ -256,13 +265,13 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
}
/**
* 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 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
}
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);
}
}
}