NSURLConnection -> NSURLSession for SDL-OSX

Code for this commit is based off of the network code in game/iphone
This commit is contained in:
Nathan Fulton 2016-03-23 11:33:17 -04:00
parent 72566c33bd
commit 8ba3738d0f
4 changed files with 264 additions and 123 deletions

View File

@ -9,22 +9,6 @@
#include "base/thread.h"
#include "base/bytebuffer.h"
namespace wi {
class MacHttpRequest;
}
@interface ConnectionDelegate : NSObject {
NSURLConnection *conn_;
wi::MacHttpRequest *req_;
}
- (void)submit;
- (void)cancel;
- (void)connection:(NSURLConnection *)conn
didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)conn;
@end
namespace wi {
class MacHttpRequest : public HttpRequest, base::MessageHandler {
@ -32,8 +16,7 @@ public:
MacHttpRequest(HttpResponseHandler *phandler);
~MacHttpRequest();
void Submit();
void Release();
void Dispose();
NSURLRequest *CreateNSURLRequest();
void OnReceivedResponse(NSHTTPURLResponse *resp);
void OnReceivedData(NSData *data);
@ -41,8 +24,22 @@ public:
void OnError(NSError *error);
private:
virtual void OnMessage(base::Message *pmsg);
HttpResponseHandler *handler_;
ConnectionDelegate *delegate_;
};
struct ReceivedResponseParams : base::MessageData {
int code;
Map headers;
};
struct ReceivedDataParams : base::MessageData {
base::ByteBuffer bb;
};
struct ErrorParams : base::MessageData {
char szError[80];
};
} // namespace wi

View File

@ -1,148 +1,151 @@
#include "game/sdl/mac/machttprequest.h"
#include "game/sdl/sysmessages.h"
#include "base/thread.h"
@implementation ConnectionDelegate
// Requests come in from the game thread; NS* api calls occur on main
// thread.
- (id)initWithRequest:(wi::MacHttpRequest *)req {
self = [super init];
if (self != nil) {
req_ = req;
conn_ = nil;
}
return self;
}
- (void)submit {
NSURLRequest *req = req_->CreateNSURLRequest();
conn_ = [NSURLConnection
connectionWithRequest:req
delegate:self];
}
- (void)cancel {
[conn_ cancel];
conn_ = nil;
}
- (void)connection:(NSURLConnection *)conn
didReceiveResponse:(NSURLResponse *)resp {
req_->OnReceivedResponse((NSHTTPURLResponse *)resp);
}
- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data {
req_->OnReceivedData(data);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)conn {
req_->OnFinishedLoading();
}
- (void)connection:(NSURLConnection *)conn
didFailWithError:(NSError *)error {
NSLog(@"error: %@", error);
req_->OnError(error);
}
@end
// C++ implementation of HttpRequest interface for Mac
// C++ implementation of HttpRequest interface for iPhone
namespace wi {
MacHttpRequest::MacHttpRequest(HttpResponseHandler *handler) :
handler_(handler), delegate_(nil) {
MacHttpRequest::MacHttpRequest(HttpResponseHandler *handler) : handler_(handler) {
}
MacHttpRequest::~MacHttpRequest() {
}
void MacHttpRequest::Submit() {
delegate_ = [[ConnectionDelegate alloc] initWithRequest:this];
[delegate_ submit];
}
void MacHttpRequest::Release() {
[delegate_ cancel];
delegate_ = nil;
Dispose();
void MacHttpRequest::Dispose() {
// Called on game thread
thread_.Clear(this);
MessageHandler::Dispose();
}
NSURLRequest *MacHttpRequest::CreateNSURLRequest() {
@autoreleasepool {
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init];
// Called on main thread
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init];
// Set the url
NSString *s = [NSString stringWithCString:url_.c_str()
encoding:[NSString defaultCStringEncoding]];
[req setURL:[NSURL URLWithString:s]];
// Set the url
NSString *s = [NSString stringWithCString:url_.c_str()
encoding:[NSString defaultCStringEncoding]];
[req setURL:[NSURL URLWithString:s]];
// Set the method
s = [NSString stringWithCString:method_.c_str()
encoding:[NSString defaultCStringEncoding]];
[req setHTTPMethod:s];
// Set the method
s = [NSString stringWithCString:method_.c_str()
encoding:[NSString defaultCStringEncoding]];
[req setHTTPMethod:s];
// Set the body
if (pbb_ != NULL) {
int cb;
void *pv = pbb_->Strip(&cb);
NSData *data = [NSData dataWithBytesNoCopy:(void *)pv length:cb];
[req setHTTPBody:data];
// Set the body
if (pbb_ != NULL) {
int cb;
void *pv = pbb_->Strip(&cb);
NSData *data = [NSData dataWithBytesNoCopy:(void *)pv length:cb];
[req setHTTPBody:data];
}
// Set timeout
[req setTimeoutInterval:timeout_];
// Set cache policy
[req setCachePolicy:NSURLRequestReloadIgnoringCacheData];
// Set headers
Enum enm;
char szKey[128];
while (headers_.EnumKeys(&enm, szKey, sizeof(szKey))) {
char szValue[256];
if (headers_.GetValue(szKey, szValue, sizeof(szValue))) {
NSString *key = [NSString stringWithCString:szKey
encoding:[NSString defaultCStringEncoding]];
NSString *value = [NSString stringWithCString:szValue
encoding:[NSString defaultCStringEncoding]];
[req setValue:value forHTTPHeaderField:key];
}
}
// Set timeout
[req setTimeoutInterval:timeout_];
// Done
[pool release];
return req;
}
// Set cache policy
[req setCachePolicy:NSURLRequestReloadIgnoringCacheData];
// Set headers
Enum enm;
char szKey[128];
while (headers_.EnumKeys(&enm, szKey, sizeof(szKey))) {
char szValue[256];
if (headers_.GetValue(szKey, szValue, sizeof(szValue))) {
NSString *key = [NSString stringWithCString:szKey
encoding:[NSString defaultCStringEncoding]];
NSString *value = [NSString stringWithCString:szValue
encoding:[NSString defaultCStringEncoding]];
[req setValue:value forHTTPHeaderField:key];
}
void MacHttpRequest::OnMessage(base::Message *pmsg) {
switch (pmsg->id) {
case kidmReceivedResponse:
{
ReceivedResponseParams *pparams =
(ReceivedResponseParams *)pmsg->data;
handler_->OnReceivedResponse(this, pparams->code,
&pparams->headers);
delete pparams;
}
break;
// Done
return req;
case kidmReceivedData:
{
ReceivedDataParams *pparams =
(ReceivedDataParams *)pmsg->data;
handler_->OnReceivedData(this, &pparams->bb);
delete pparams;
}
break;
case kidmFinishedLoading:
handler_->OnFinishedLoading(this);
break;
case kidmError:
{
ErrorParams *pparams = (ErrorParams *)pmsg->data;
handler_->OnError(this, pparams->szError);
delete pparams;
}
break;
}
}
void MacHttpRequest::OnReceivedResponse(NSHTTPURLResponse *resp) {
Map headers;
// Called on main thread. Populate ReceivedResponseParams
ReceivedResponseParams *pparams = new ReceivedResponseParams;
NSDictionary *dict = [resp allHeaderFields];
for (NSString *k in dict) {
NSString *v = [dict objectForKey:k];
headers.SetValue(
pparams->headers.SetValue(
[k cStringUsingEncoding:[NSString defaultCStringEncoding]],
[v cStringUsingEncoding:[NSString defaultCStringEncoding]]);
}
int code = [resp statusCode];
handler_->OnReceivedResponse(this, code, &headers);
pparams->code = (int)[resp statusCode];
// Post this to the game thread
thread_.Post(kidmReceivedResponse, this, pparams);
}
void MacHttpRequest::OnReceivedData(NSData *data) {
base::ByteBuffer bb;
bb.WriteBytes((const byte *)[data bytes], [data length]);
handler_->OnReceivedData(this, &bb);
// Called on main thread. Populate ReceivedDataParams
ReceivedDataParams *pparams = new ReceivedDataParams;
pparams->bb.WriteBytes((const byte *)[data bytes], (int)[data length]);
// Post this to the game thread
thread_.Post(kidmReceivedData, this, pparams);
}
void MacHttpRequest::OnFinishedLoading() {
handler_->OnFinishedLoading(this);
// Called on main thread. Post this to game thread.
thread_.Post(kidmFinishedLoading, this);
}
void MacHttpRequest::OnError(NSError *error) {
// Called on main thread. Populate ErrorParams. Use
// localizedDescription. Note there is also localizedFailureReason;
// not sure which is better at the moment
const char *psz = [[error localizedDescription] cStringUsingEncoding:
[NSString defaultCStringEncoding]];
char szError[80];
strncpyz(szError, psz, sizeof(szError));
handler_->OnError(this, szError);
ErrorParams *pparams = new ErrorParams;
strncpyz(pparams->szError, psz, sizeof(pparams->szError));
// Called on main thread. Post this to game thread.
thread_.Post(kidmError, this, pparams);
}
} // namespace wi

View File

@ -8,10 +8,15 @@ namespace wi {
class MacHttpService : public HttpService {
public:
MacHttpService();
// HttpService methods
virtual HttpRequest *NewRequest(HttpResponseHandler *phandler);
virtual void SubmitRequest(HttpRequest *preq);
virtual void ReleaseRequest(HttpRequest *preq);
private:
void *delegate_;
};
} // namespace wi

View File

@ -1,20 +1,156 @@
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include "game/sdl/mac/machttpservice.h"
#include "game/sdl/mac/machttprequest.h"
#include "game/sdl/sysmessages.h"
#include "game/sdl/hosthelpers.h"
#include <map>
typedef std::map<NSURLSessionDataTask *, wi::MacHttpRequest *> TaskMap;
// HttpService calls come in on the game thread. In order to use
// iPhone NS* Http apis, requests execute on the main thread.
@interface MacHttpRequestWrapper : NSObject {
wi::MacHttpRequest *req_;
}
@end
@implementation MacHttpRequestWrapper
- (id)initWithRequest:(wi::MacHttpRequest *)req {
self = [super init];
if (self != nil) {
req_ = req;
}
return self;
}
- (wi::MacHttpRequest *)request {
return req_;
}
@end
@interface SessionDelegate : NSObject<NSURLSessionDataDelegate> {
NSURLSession *session_;
TaskMap taskmap_;
}
@end
@implementation SessionDelegate
- (id)init {
self = [super init];
if (self != nil) {
session_ = nil;
}
return self;
}
- (void)dealloc {
[session_ release];
[super dealloc];
}
- (void)cancel:(MacHttpRequestWrapper *)wrapper {
wi::MacHttpRequest *req = [wrapper request];
NSURLSessionDataTask *task = nil;
TaskMap::iterator it = taskmap_.begin();
for (; it != taskmap_.end(); it++) {
if (it->second == req) {
task = it->first;
taskmap_.erase(it);
break;
}
}
if (task != nil) {
[task cancel];
[task release];
}
[wrapper release];
}
- (void)submit:(MacHttpRequestWrapper *)wrapper {
if (session_ == nil) {
session_ = [NSURLSession
sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
wi::MacHttpRequest *req = [wrapper request];
NSURLRequest *url_req = req->CreateNSURLRequest();
NSURLSessionDataTask *task = [session_ dataTaskWithRequest:url_req];
[url_req release];
[task retain];
taskmap_.insert(TaskMap::value_type(task, req));
[task resume];
[wrapper release];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:(NSURLResponse *)resp
completionHandler:(void (^)(NSURLSessionResponseDisposition disp))handler {
TaskMap::iterator it = taskmap_.find(task);
if (it != taskmap_.end()) {
it->second->OnReceivedResponse((NSHTTPURLResponse *)resp);
}
handler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
didReceiveData:(NSData *)data {
TaskMap::iterator it = taskmap_.find(task);
if (it != taskmap_.end()) {
it->second->OnReceivedData(data);
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
TaskMap::iterator it = taskmap_.find((NSURLSessionDataTask *)task);
if (it != taskmap_.end()) {
if (error == nil) {
it->second->OnFinishedLoading();
} else {
NSLog(@"error: %@", error);
it->second->OnError(error);
}
}
}
@end
namespace wi {
MacHttpService::MacHttpService() {
delegate_ = NULL;
}
HttpRequest *MacHttpService::NewRequest(HttpResponseHandler *phandler) {
return new MacHttpRequest(phandler);
}
void MacHttpService::SubmitRequest(HttpRequest *preq) {
MacHttpRequest *preqT = (MacHttpRequest *)preq;
preqT->Submit();
if (delegate_ == NULL) {
delegate_ = [[SessionDelegate alloc] init];
}
MacHttpRequestWrapper *wrapper = [[MacHttpRequestWrapper alloc]
initWithRequest:(MacHttpRequest *)preq];
SessionDelegate *delegate = (SessionDelegate *)delegate_;
[delegate performSelectorOnMainThread:@selector(submit:)
withObject:wrapper waitUntilDone: NO];
}
void MacHttpService::ReleaseRequest(HttpRequest *preq) {
MacHttpRequest *preqT = (MacHttpRequest *)preq;
preqT->Release();
// This can cause a deadlock when exiting because of how the main thread
// is synchronizing with the game thread to exit before it does
MacHttpRequest *req = (MacHttpRequest *)preq;
if (!HostHelpers::IsExiting()) {
MacHttpRequestWrapper *wrapper = [[MacHttpRequestWrapper alloc]
initWithRequest:req];
SessionDelegate *delegate = (SessionDelegate *)delegate_;
[delegate performSelectorOnMainThread:@selector(cancel:)
withObject:wrapper waitUntilDone: YES];
}
req->Dispose();
}
} // namespace wi