blob: 17fe90adc2a7536d4f361c8f50ba4e5a98677df2 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "url_loader_impl.h"
#include "base/logging.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#import <Foundation/Foundation.h>
@interface URLLoaderConnectionDelegate : NSObject<NSURLConnectionDataDelegate>
@property(nonatomic) mojo::URLLoaderImpl::StartCallback startCallback;
@property(nonatomic, retain) NSURLRequest *originalRequest;
@end
@implementation URLLoaderConnectionDelegate {
mojo::URLResponsePtr _response;
mojo::ScopedDataPipeProducerHandle _producer;
}
@synthesize startCallback = _startCallback;
@synthesize originalRequest = _originalRequest;
- (void)connection:(NSURLConnection*)connection
didReceiveResponse:(NSHTTPURLResponse*)response {
_response = mojo::URLResponse::New();
_response->status_code = response.statusCode;
_response->url =
mojo::String(self.originalRequest.URL.absoluteString.UTF8String);
mojo::DataPipe pipe;
_response->body = pipe.consumer_handle.Pass();
_producer = pipe.producer_handle.Pass();
[self.class updateDelegate:self asPending:YES];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
if (!_startCallback.is_null()) {
DCHECK(_response);
_startCallback.Run(_response.Pass());
_startCallback.reset();
_response.reset();
}
uint32_t length = data.length;
// TODO(eseidel): This can't work. The data pipe could be full, we need to
// write an async writter for filling the pipe and use it here.
MojoResult result = WriteDataRaw(_producer.get(), data.bytes, &length,
MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
// FIXME(csg): Handle buffers in case of failures
DCHECK(result == MOJO_RESULT_OK);
DCHECK(length == data.length);
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
DCHECK(_response.is_null());
DCHECK(_startCallback.is_null());
_producer.reset();
[self.class updateDelegate:self asPending:NO];
}
- (void)connection:(NSURLConnection*)connection
didFailWithError:(NSError*)error {
if (!_startCallback.is_null()) {
if (_response.is_null()) {
_response = mojo::URLResponse::New();
_response->url = mojo::String(
self.originalRequest.URL.absoluteString.UTF8String);
}
_response->error = mojo::NetworkError::New();
_response->error->description =
mojo::String(error.localizedDescription.UTF8String);
_startCallback.Run(_response.Pass());
_startCallback.reset();
}
_response.reset();
_producer.reset();
[self.class updateDelegate:self asPending:NO];
}
// Since the only reference to the producer end of a data pipe is held by the
// delegate, which itself has no strong reference, we put the in-flight requests
// in a collection that references these delegates while they are active.
+ (void)updateDelegate:(URLLoaderConnectionDelegate*)delegate
asPending:(BOOL)pending {
static NSMutableSet* pendingConnections = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pendingConnections = [[NSMutableSet alloc] init];
});
if (pending) {
[pendingConnections addObject:delegate];
} else {
[pendingConnections removeObject:delegate];
}
}
- (void)dealloc {
[_originalRequest release];
DCHECK(_response.is_null());
DCHECK(_startCallback.is_null());
_producer.reset();
[super dealloc];
}
@end
namespace mojo {
URLLoaderImpl::URLLoaderImpl(InterfaceRequest<URLLoader> request)
: binding_(this, request.Pass()),
connection_delegate_(nullptr),
pending_connection_(nullptr) {
connection_delegate_ = [[URLLoaderConnectionDelegate alloc] init];
}
URLLoaderImpl::~URLLoaderImpl() {
[(id)connection_delegate_ release];
[(id)pending_connection_ cancel];
[(id)pending_connection_ release];
}
void URLLoaderImpl::Start(URLRequestPtr request,
const StartCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;
NSURL* url = [NSURL URLWithString:@(request->url.data())];
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
req.HTTPMethod = @(request->method.data());
URLLoaderConnectionDelegate* delegate =
(URLLoaderConnectionDelegate*)connection_delegate_;
NSURLConnection* connection =
[NSURLConnection connectionWithRequest:req delegate:delegate];
delegate.startCallback = callback;
delegate.originalRequest = req;
[connection start];
pending_connection_ = [connection retain];
}
void URLLoaderImpl::FollowRedirect(const FollowRedirectCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;
DCHECK(false);
}
void URLLoaderImpl::QueryStatus(const QueryStatusCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;
DCHECK(false);
}
} // namespace mojo