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:
parent
a4bb4d25f5
commit
be4afdde37
|
@ -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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue