Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions webchannel/objc/WebChannel.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Pod::Spec.new do |s|
s.name = 'WebChannel'
s.version = '0.1.0'
s.summary = 'Objective-C client for WebChannel'
s.description = <<-DESC
Objective-C client implementation for WebChannel, providing robust bidirectional
communication over HTTP.
DESC
s.homepage = 'https://github.com/google/net_http'
s.license = { :type => 'Apache License, Version 2.0', :file => '../../LICENSE' }
s.author = { 'Google LLC' => 'webchannel-dev@google.com' }
s.source = { :git => 'https://github.com/google/net_http.git', :tag => s.version.to_s }

s.ios.deployment_target = '12.0'
s.osx.deployment_target = '10.15'

s.source_files = 'imported_src/**/*.{h,m}'
s.exclude_files = 'imported_src/Tests/**/*.{h,m}'

s.dependency 'GTMSessionFetcher', '~> 3.0'

s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'imported_src/Tests/**/*.{h,m}'
test_spec.dependency 'OCMock', '~> 3.0'
end
end
13 changes: 13 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultHTTPRequest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

#import "WCHTTPRequest.h"

@class GTMSessionFetcherService;

@interface WCDefaultHTTPRequest : NSObject <WCHTTPRequest>

- (instancetype)initWithStateChangeHandler:(id<WCRequestStateChangedHandler>)handler
dispatchQueue:(dispatch_queue_t)dispatchQueue
fetcherService:(nonnull GTMSessionFetcherService *)fetcherService;

@end
135 changes: 135 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultHTTPRequest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#import "WCDefaultHTTPRequest.h"

#import "WCHTTPRequest.h"
#import <GTMSessionFetcher/GTMSessionFetcher.h>
#import <GTMSessionFetcher/GTMSessionFetcherService.h>

@implementation WCDefaultHTTPRequest {
// Internal queue used to handle all Webchannel logic to ensure thread safety.
dispatch_queue_t _dispatchQueue;
GTMSessionFetcherService *_fetcherService;

GTMSessionFetcher *_fetcher;
NSHTTPURLResponse *_Nullable _response;
}

@synthesize requestReadyStateChangeHandler = _requestReadyStateChangeHandler;
@synthesize requestReadyState = _requestReadyState;
@synthesize requestErrorCode = _requestErrorCode;

- (instancetype)initWithStateChangeHandler:(id<WCRequestStateChangedHandler>)handler
dispatchQueue:(dispatch_queue_t)dispatchQueue
fetcherService:(nonnull GTMSessionFetcherService *)fetcherService {
self = [super init];
if (self) {
_requestReadyStateChangeHandler = handler;
_dispatchQueue = dispatchQueue;
_fetcherService = fetcherService;
_requestErrorCode = WCRequestErrorCodeNoError;
}
return self;
}

- (NSString *)responseHeaderForName:(NSString *)name {
return [_response valueForHTTPHeaderField:name];
}

- (int)status {
return (int)_fetcher.statusCode;
}

#pragma mark - WCHTTPRequest

- (void)sendPOST:(NSURL *)URL
withData:(NSData *)POSTData
withHeaders:(NSDictionary<NSString *, NSString *> *)headers
timeout:(NSTimeInterval)timeout {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.HTTPMethod = @"POST";
request.timeoutInterval = timeout;
request.HTTPBody = POSTData;
request.allHTTPHeaderFields = headers;

[self beginFetchWithRequest:request];
}

- (void)sendGET:(NSURL *)URL
withHeaders:(NSDictionary<NSString *, NSString *> *)headers
timeout:(NSTimeInterval)timeout {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.HTTPMethod = @"GET";
request.timeoutInterval = timeout;
request.allHTTPHeaderFields = headers;

[self beginFetchWithRequest:request];
}

- (void)abort {
[_fetcher stopFetching];
}

- (void)beginFetchWithRequest:(NSURLRequest *)request {
_fetcher = [_fetcherService fetcherWithRequest:request];
_fetcher.callbackQueue = _dispatchQueue;
#if DEBUG
_fetcher.allowLocalhostRequest = YES;
#endif

__weak WCDefaultHTTPRequest *weakSelf = self;
_fetcher.didReceiveResponseBlock =
^(NSURLResponse *response,
GTMSessionFetcherDidReceiveResponseDispositionBlock completionHandler) {
WCDefaultHTTPRequest *strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_response = (NSHTTPURLResponse *)response;
strongSelf->_requestReadyState = WCRequestReadyStateLoaded;
[strongSelf->_requestReadyStateChangeHandler stateChangedForRequest:strongSelf
responseData:NULL];
}
completionHandler(NSURLSessionResponseAllow);
};

_fetcher.accumulateDataBlock = ^(NSData *data) {
WCDefaultHTTPRequest *strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_requestReadyState = WCRequestReadyStateInteractive;
[strongSelf->_requestReadyStateChangeHandler stateChangedForRequest:strongSelf
responseData:data];
}
};

[_fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
WCDefaultHTTPRequest *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (error) {
strongSelf->_requestErrorCode = [strongSelf errorCodeForError:error];
} else {
if (strongSelf->_fetcher.statusCode != 200) {
strongSelf->_requestErrorCode = WCRequestErrorCodeHTTPError;
}
}
strongSelf->_requestReadyState = WCRequestReadyStateComplete;
[strongSelf->_requestReadyStateChangeHandler stateChangedForRequest:strongSelf
responseData:NULL];
}];
}

- (WCRequestErrorCode)errorCodeForError:(NSError *)error {
if ([error.domain isEqual:kGTMSessionFetcherStatusDomain]) {
return WCRequestErrorCodeHTTPError;
} else if ([error.domain isEqual:NSURLErrorDomain]) {
if (error.code == NSURLErrorTimedOut) {
return WCRequestErrorCodeTimeout;
} else if (error.code == NSURLErrorCancelled) {
return WCRequestErrorCodeAbort;
} else {
return WCRequestErrorCodeException;
}
} else {
return WCRequestErrorCodeException;
}
}

@end
6 changes: 6 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultJSONDecoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>

#import "WCJSONDecoder.h"

@interface WCDefaultJSONDecoder : NSObject <WCJSONDecoder>
@end
18 changes: 18 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultJSONDecoder.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "WCDefaultJSONDecoder.h"

@implementation WCDefaultJSONDecoder

- (NSArray *)decodeData:(NSString *)data maxDepth:(int)maxDepth {
// TODO(eryu): Refactor to avoid inefficient NSData->NSString->NSData conversion.
// WCChannelRequest converts network response from NSData to NSString before
// calling its delegate's -didReceiveInput:withRequest: method. The input
// eventually reaches this method as NSString, which is converted back to
// NSData because the underlying JSON parser (NSJSONSerialization) requires
// NSData. This data path should be optimized to pass NSData directly to
// eliminate the redundant conversions.
NSData *encodingData = [data dataUsingEncoding:NSUTF8StringEncoding];
NSArray<id> *JSONArray = [NSJSONSerialization JSONObjectWithData:encodingData options:0 error:nil];
return JSONArray;
}

@end
42 changes: 42 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#import <Foundation/Foundation.h>

#import "WCLogger.h"

extern BOOL gWCVerboseLoggingEnabled;

/** Convenience macro of Debug. */
#ifndef WCDefaultLoggerDebug

#define WCDefaultLoggerDebug(...) \
if (gWCVerboseLoggingEnabled) { \
NSLog(__VA_ARGS__); \
}

#ifndef DEBUG
#undef WCDefaultLoggerDebug
#define WCDefaultLoggerDebug(...) \
do { \
} while (0)
#endif

#endif

/** Convenience macro of Info. */
#ifndef WCDefaultLoggerInfo

#define WCDefaultLoggerInfo(...) NSLog(__VA_ARGS__);

#endif

/** Convenience macro of Error. */
#ifndef WCDefaultLoggerError

#define WCDefaultLoggerError(...) \
if (gWCVerboseLoggingEnabled) { \
NSLog(__VA_ARGS__); \
}

#endif

@interface WCDefaultLogger : NSObject <WCLogger>
@end
83 changes: 83 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultLogger.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#import "WCDefaultLogger.h"
#import "WCHTTPRequest.h"

#if WC_VERBOSE_LOGGING
BOOL gWCVerboseLoggingEnabled = YES;
#else
BOOL gWCVerboseLoggingEnabled = NO;
#endif // WC_VERBOSE_LOGGING

@implementation WCDefaultLogger

- (void)logInfo:(NSString *)message {
WCDefaultLoggerInfo(@"%@", message);
}

- (void)logDebug:(NSString *)message {
WCDefaultLoggerDebug(@"%@", message);
}

- (void)logWarning:(NSString *)message {
WCDefaultLoggerInfo(@"Warning: %@", message);
}

- (void)dumpException:(NSException *)e withMessage:(NSString *)message {
WCDefaultLoggerError(@"Exception:%@: %@", e.name, message);
}

- (void)logError:(NSString *)message {
WCDefaultLoggerError(@"%@", message);
}

- (void)logHTTPRequest:(NSString *)verb
URL:(NSURL *)URL
ID:(NSString *)ID
attempt:(int64_t)attempt
postData:(NSString *)postData {
NSString *text = [NSString stringWithFormat:@"XMLHTTP REQ (%@) [attempt %lld ]: %@ \n %@ \n %@", ID,
attempt, verb, URL.absoluteString, postData];
[self logDebug:text];
}

- (void)logHTTPChannelResponseMetaData:(NSString *)verb
URL:(NSURL *)URL
ID:(NSString *)ID
attempt:(int64_t)attempt
state:(WCRequestReadyState)state
statusCode:(int32_t)code {
NSString *text =
[NSString stringWithFormat:@"XMLHTTP RESP (%@) [attempt %lld ]: %@ \n %@ \n %@ %d", ID,
attempt, verb, URL.absoluteString,
[self formatRequestReadyStateToString:state], code];
[self logDebug:text];
}

- (NSString*)formatRequestReadyStateToString:(WCRequestReadyState)state {
NSString *result = nil;
switch(state) {
case WCRequestReadyStateUninitialized:
result = @"RequestReadyState Uninitialized";
break;
case WCRequestReadyStateLoaded:
result = @"WCRequestReadyState Loaded";
break;
case WCRequestReadyStateInteractive:
result = @"WCRequestReadyState Interactive";
break;
case WCRequestReadyStateComplete:
result = @"RequestReadyState Complete";
break;
default:
result = @"Unexpected state";
}
return result;
}

- (void)logHTTPChannelResponseText:(NSString *)responseText
ID:(NSString *)ID
desc:(NSString *)desc {
NSString *text = [NSString stringWithFormat:@"XMLHTTP RESP (%@): %@ %@", ID, responseText, desc];
[self logDebug:text];
}

@end
6 changes: 6 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultURLEncoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>

#import "WCURLEncoder.h"

@interface WCDefaultURLEncoder : NSObject <WCURLEncoder>
@end
10 changes: 10 additions & 0 deletions webchannel/objc/imported_src/Support/WCDefaultURLEncoder.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "WCDefaultURLEncoder.h"

@implementation WCDefaultURLEncoder

- (NSString *)encode:(NSString *)data {
return [data
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];
}

@end
7 changes: 7 additions & 0 deletions webchannel/objc/imported_src/Support/WCFakeHTTPRequest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

#import "WCHTTPRequest.h"

@interface WCFakeHTTPRequest : NSObject <WCHTTPRequest>

@end
33 changes: 33 additions & 0 deletions webchannel/objc/imported_src/Support/WCFakeHTTPRequest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#import "WCFakeHTTPRequest.h"

#import "WCHTTPRequest.h"

@implementation WCFakeHTTPRequest

@synthesize requestReadyStateChangeHandler = _requestReadyStateChangeHandler;
@synthesize requestReadyState = _requestReadyState;
@synthesize requestErrorCode = _requestErrorCode;

- (NSString *)responseHeaderForName:(NSString *)name {
return @"";
}

- (int)status {
return 200;
}

- (void)sendPOST:(NSURL *)URL
withData:(nullable NSData *)postData
withHeaders:(nullable NSDictionary<NSString *, NSString *> *)headers
timeout:(NSTimeInterval)timeout{
[_requestReadyStateChangeHandler stateChangedForRequest:self responseData:nil];
}

- (void)sendGET:(NSURL *)URL withHeaders:(nullable NSDictionary<NSString *, NSString *> *)headers timeout:(NSTimeInterval)timeout {
[_requestReadyStateChangeHandler stateChangedForRequest:self responseData:nil];
}

- (void)abort {
}

@end
Loading