From 205b5d4732747bac09ebc0f67ed1b1e430ec70fc Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Tue, 15 Mar 2016 05:13:51 -0700 Subject: [PATCH] Update options parameter to headers. Update to spec. Summary:This is a follow up of https://github.com/facebook/react-native/commit/9b87e6c860a95fe3d55285314d3d56be06cb7833. - Allows custom headers on connection request - Adds a default `origin` header to Android, just like iOS **Introduces no breaking changes.** I was working on something similar and would like to propose a few changes that make the API more consistent across both iOS and Android platforms and brings this closer to [spec](https://tools.ietf.org/html/rfc6455). I believe aprock first implementation of adding custom `headers` was correct. It makes sense naming this argument `headers` since we have no other general options available, and the current `options` field is being used to pass in a header anyway. My use case for custom headers was attaching a token to the `Authorization` header on the connection request. I have been testing this by passing a JWT inside the `Authorization` header and verifying it on the server before establishing a connection. Closes https://github.com/facebook/react-native/pull/6016 Differential Revision: D3040735 Pulled By: nicklockwood fb-gh-sync-id: f81bd14ccbdba36309b9d4b4850fb66fe4deae11 shipit-source-id: f81bd14ccbdba36309b9d4b4850fb66fe4deae11 --- Libraries/WebSocket/RCTSRWebSocket.h | 8 +-- Libraries/WebSocket/RCTSRWebSocket.m | 19 ++---- Libraries/WebSocket/RCTWebSocketModule.m | 10 ++- Libraries/WebSocket/WebSocket.js | 4 +- .../modules/websocket/WebSocketModule.java | 62 ++++++++++++++++--- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/Libraries/WebSocket/RCTSRWebSocket.h b/Libraries/WebSocket/RCTSRWebSocket.h index 12753043470e32..1b17cffaf47c08 100644 --- a/Libraries/WebSocket/RCTSRWebSocket.h +++ b/Libraries/WebSocket/RCTSRWebSocket.h @@ -60,19 +60,17 @@ extern NSString *const RCTSRHTTPResponseErrorKey; @property (nonatomic, readonly, copy) NSString *protocol; // Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. -// options can contain a custom "origin" NSString -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols options:(NSDictionary *)options NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols NS_DESIGNATED_INITIALIZER; - (instancetype)initWithURLRequest:(NSURLRequest *)request; // Some helper constructors. -- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols options:(NSDictionary *)options; - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; - (instancetype)initWithURL:(NSURL *)url; // Delegate queue will be dispatch_main_queue by default. // You cannot set both OperationQueue and dispatch_queue. -- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; -- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; +- (void)setDelegateOperationQueue:(NSOperationQueue *)queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; // By default, it will schedule itself on +[NSRunLoop RCTSR_networkRunLoop] using defaultModes. - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m index bf67c3b14e3748..fe02d069768db7 100644 --- a/Libraries/WebSocket/RCTSRWebSocket.m +++ b/Libraries/WebSocket/RCTSRWebSocket.m @@ -234,7 +234,6 @@ @implementation RCTSRWebSocket __strong RCTSRWebSocket *_selfRetain; NSArray *_requestedProtocols; - NSDictionary *_requestedOptions; RCTSRIOConsumerPool *_consumerPool; } @@ -245,7 +244,7 @@ + (void)initialize; CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; } -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols options:(NSDictionary *)options +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols { RCTAssertParam(request); @@ -254,7 +253,6 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; -{ - return [self initWithURL:URL protocols:protocols options:nil]; -} - -- (instancetype)initWithURL:(NSURL *)URL protocols:(NSArray *)protocols options:(NSDictionary *)options { NSMutableURLRequest *request; if (URL) { @@ -297,7 +290,7 @@ - (instancetype)initWithURL:(NSURL *)URL protocols:(NSArray *)protoc NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:components.URL]; [request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]]; } - return [self initWithURLRequest:request protocols:protocols options:options]; + return [self initWithURLRequest:request protocols:protocols]; } - (void)_RCTSR_commonInit; @@ -488,12 +481,12 @@ - (void)didConnect CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.RCTSR_origin); + if (_requestedProtocols) { CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); } - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)(_requestedOptions[@"origin"] ?: _url.RCTSR_origin)); - [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); }]; diff --git a/Libraries/WebSocket/RCTWebSocketModule.m b/Libraries/WebSocket/RCTWebSocketModule.m index 6e057f78b148c7..aa5d0e412c5e50 100644 --- a/Libraries/WebSocket/RCTWebSocketModule.m +++ b/Libraries/WebSocket/RCTWebSocketModule.m @@ -11,6 +11,7 @@ #import "RCTBridge.h" #import "RCTEventDispatcher.h" +#import "RCTConvert.h" #import "RCTUtils.h" @implementation RCTSRWebSocket (React) @@ -44,9 +45,14 @@ - (void)dealloc } } -RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols options:(NSDictionary *)options socketID:(nonnull NSNumber *)socketID) +RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols headers:(NSDictionary *)headers socketID:(nonnull NSNumber *)socketID) { - RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURL:URL protocols:protocols options:options]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { + [request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key]; + }]; + + RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:request protocols:protocols]; webSocket.delegate = self; webSocket.reactTag = socketID; if (!_sockets) { diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js index ec6f722ef7a8ab..905a9f6558f78b 100644 --- a/Libraries/WebSocket/WebSocket.js +++ b/Libraries/WebSocket/WebSocket.js @@ -33,10 +33,10 @@ class WebSocket extends WebSocketBase { _socketId: number; _subs: any; - connectToSocketImpl(url: string, protocols: ?Array, options: ?{origin?: string}): void { + connectToSocketImpl(url: string, protocols: ?Array, headers: ?Object): void { this._socketId = WebSocketId++; - RCTWebSocketModule.connect(url, protocols, options, this._socketId); + RCTWebSocketModule.connect(url, protocols, headers, this._socketId); this._registerEvents(this._socketId); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java index af8d79597884e5..1d9d1dda124e80 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java @@ -34,6 +34,8 @@ import com.squareup.okhttp.ws.WebSocketCall; import com.squareup.okhttp.ws.WebSocketListener; +import java.net.URISyntaxException; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -63,7 +65,7 @@ public String getName() { } @ReactMethod - public void connect(final String url, @Nullable final ReadableArray protocols, @Nullable final ReadableMap options, final int id) { + public void connect(final String url, @Nullable final ReadableArray protocols, @Nullable final ReadableMap headers, final int id) { // ignoring protocols, since OKHttp overrides them. OkHttpClient client = new OkHttpClient(); @@ -76,14 +78,25 @@ public void connect(final String url, @Nullable final ReadableArray protocols, @ .tag(id) .url(url); - if (options != null && options.hasKey("origin")) { - if (ReadableType.String.equals(options.getType("origin"))) { - builder.addHeader("Origin", options.getString("origin")); - } else { - FLog.w( - ReactConstants.TAG, - "Ignoring: requested origin, value not a string"); + if (headers != null) { + ReadableMapKeySetIterator iterator = headers.keySetIterator(); + + if (!headers.hasKey("origin")) { + builder.addHeader("origin", setDefaultOrigin(url)); } + + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + if (ReadableType.String.equals(headers.getType(key))) { + builder.addHeader(key, headers.getString(key)); + } else { + FLog.w( + ReactConstants.TAG, + "Ignoring: requested " + key + ", value not a string"); + } + } + } else { + builder.addHeader("origin", setDefaultOrigin(url)); } WebSocketCall.create(client, builder.build()).enqueue(new WebSocketListener() { @@ -188,4 +201,37 @@ private void notifyWebSocketFailed(int id, String message) { params.putString("message", message); sendEvent("websocketFailed", params); } + + /** + * Set a default origin + * + * @param Websocket connection endpoint + * @return A string of the endpoint converted to HTTP protocol + */ + + private static String setDefaultOrigin(String uri) { + try { + String defaultOrigin; + String scheme = ""; + + URI requestURI = new URI(uri); + if (requestURI.getScheme().equals("wss")) { + scheme += "https"; + } else if (requestURI.getScheme().equals("ws")) { + scheme += "http"; + } + + if (requestURI.getPort() != -1) { + defaultOrigin = String.format("%s://%s:%s", scheme, requestURI.getHost(), requestURI.getPort()); + } else { + defaultOrigin = String.format("%s://%s/", scheme, requestURI.getHost()); + } + + return defaultOrigin; + + } catch(URISyntaxException e) { + throw new IllegalArgumentException("Unable to set " + uri + " as default origin header."); + } + } + }