Skip to content

Commit

Permalink
Implement SPDY Push.
Browse files Browse the repository at this point in the history
This changeset introduces support for SPDY server push streams. It is
heavily based off work done by Layer, but modified to expose a richer
interface to the application. It also undos the SPDYSessionManager
changes.

Many changes went in SPDYStream in order to support both the
NSURLConnection delegate model, and the new CocoaSPDY push stream
delegate model. The app can register for SPDYExtendedDelegate callbacks
on each NSURLRequest. That, in turn, allows the app to be notified of
new push streams, and to subsequently register for notifications of
those via a SPDYPushResponseDataDelegate or simpler completion block.
Fire-and-forget caching is also an option.

Additional usage notes are in SPDYProtocol.h.

Stream closing was substantially changed internally.

Better support for HEADERS frames is included here.

Unit tests for pushed streams are included here along with all the
necessary mocks. It's a bit complicated, but has been simplified as much
as possible with helpers. A number of small changes and fixes were made
to the code as unit tests were developed.
  • Loading branch information
kgoodier committed Oct 1, 2014
1 parent 6c5103a commit 46e8cbe
Show file tree
Hide file tree
Showing 21 changed files with 2,227 additions and 232 deletions.
48 changes: 46 additions & 2 deletions SPDY.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,22 @@
06FDA21716717F5E00137DBD /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2CC14D2161B608C002E37CF /* CFNetwork.framework */; };
25959A3F1937DE3900FC9731 /* SPDYSessionManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 25959A3E1937DE3900FC9731 /* SPDYSessionManagerTest.m */; };
5C2229591952257800CAF160 /* SPDYURLRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2229581952257800CAF160 /* SPDYURLRequestTest.m */; };
5CDA35B819DA330700D3CAAA /* SPDYMockExtendedDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CDA35B619DA330700D3CAAA /* SPDYMockExtendedDelegate.m */; };
7774C1318AB029C6BCEF84D6 /* SPDYSessionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C2026A4DE9957D75F629 /* SPDYSessionTest.m */; };
7774C1F1E544793907908882 /* SPDYMockFrameEncoderDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C69089A6978113F0C275 /* SPDYMockFrameEncoderDelegate.m */; };
7774C1F5D4F433D4563708C3 /* SPDYCanonicalRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774C111AB944B90A850CD41 /* SPDYCanonicalRequest.h */; };
7774C2528AAEAE6670822068 /* SPDYCanonicalRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C8410DDDA27D92CB970F /* SPDYCanonicalRequest.m */; };
7774C2FBEA6973E90CAE7005 /* SPDYMockSessionTestBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C193AC525BC3A79F2853 /* SPDYMockSessionTestBase.m */; };
7774C49E56F7198554B57FD3 /* SPDYCanonicalRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C8410DDDA27D92CB970F /* SPDYCanonicalRequest.m */; };
7774C517D2EED111E723966D /* SPDYCacheStoragePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CF2E562FD9368A064F75 /* SPDYCacheStoragePolicy.m */; };
7774C69B8FAC0669C2D79CD5 /* SPDYCanonicalRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774C111AB944B90A850CD41 /* SPDYCanonicalRequest.h */; };
7774C6D327298AADBC891CF0 /* SPDYCacheStoragePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CF2E562FD9368A064F75 /* SPDYCacheStoragePolicy.m */; };
7774CA1FA1F4A59CA0906BB7 /* SPDYSocket+SPDYSocketMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C0ECD0C6E5D73FB38752 /* SPDYSocket+SPDYSocketMock.m */; };
7774CAE01D37B13513AC3C60 /* SPDYCacheStoragePolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774C5A6A63982953028D4FF /* SPDYCacheStoragePolicy.h */; };
7774CB666CC0ECB10B6EB681 /* SPDYCacheStoragePolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774C5A6A63982953028D4FF /* SPDYCacheStoragePolicy.h */; };
7774CE1EBC87500DBA40806D /* SPDYCacheStoragePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CF2E562FD9368A064F75 /* SPDYCacheStoragePolicy.m */; };
7774CEA5AB5D1536D0CDCEA4 /* SPDYServerPushTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C803F0948788139CD2C1 /* SPDYServerPushTest.m */; };
7774CFD160EC2F7B74349445 /* SPDYCanonicalRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C8410DDDA27D92CB970F /* SPDYCanonicalRequest.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -141,11 +154,20 @@
4FE891C7065B348CC7EF4BFC /* SPDYHeaderBlockDecompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYHeaderBlockDecompressor.h; sourceTree = "<group>"; };
4FE895BE087AC28BBBC857F9 /* SPDYHeaderBlockDecompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYHeaderBlockDecompressor.m; sourceTree = "<group>"; };
5C2229581952257800CAF160 /* SPDYURLRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYURLRequestTest.m; sourceTree = "<group>"; };
5CDA35B319DA32D900D3CAAA /* SPDYMockExtendedDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYMockExtendedDelegate.h; sourceTree = "<group>"; };
5CDA35B619DA330700D3CAAA /* SPDYMockExtendedDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMockExtendedDelegate.m; sourceTree = "<group>"; };
7774C0ECD0C6E5D73FB38752 /* SPDYSocket+SPDYSocketMock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SPDYSocket+SPDYSocketMock.m"; sourceTree = "<group>"; };
7774C111AB944B90A850CD41 /* SPDYCanonicalRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYCanonicalRequest.h; sourceTree = "<group>"; };
7774C193AC525BC3A79F2853 /* SPDYMockSessionTestBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMockSessionTestBase.m; sourceTree = "<group>"; };
7774C2026A4DE9957D75F629 /* SPDYSessionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYSessionTest.m; sourceTree = "<group>"; };
7774C5A6A63982953028D4FF /* SPDYCacheStoragePolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYCacheStoragePolicy.h; sourceTree = "<group>"; };
7774C69089A6978113F0C275 /* SPDYMockFrameEncoderDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMockFrameEncoderDelegate.m; sourceTree = "<group>"; };
7774C7E1AF717FC36B7F15B6 /* SPDYSocket+SPDYSocketMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SPDYSocket+SPDYSocketMock.h"; sourceTree = "<group>"; };
7774C803F0948788139CD2C1 /* SPDYServerPushTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYServerPushTest.m; sourceTree = "<group>"; };
7774C8410DDDA27D92CB970F /* SPDYCanonicalRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYCanonicalRequest.m; sourceTree = "<group>"; };
7774CD0A3295C8E314D6E3FF /* SPDYMockFrameEncoderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYMockFrameEncoderDelegate.h; sourceTree = "<group>"; };
7774CF2E562FD9368A064F75 /* SPDYCacheStoragePolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYCacheStoragePolicy.m; sourceTree = "<group>"; };
7774CFEA3D0DAF374D7C7654 /* SPDYMockSessionTestBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYMockSessionTestBase.h; sourceTree = "<group>"; };
D2CC14B216179B43002E37CF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D2CC14B816179B43002E37CF /* SPDY-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SPDY-Prefix.pch"; sourceTree = "<group>"; };
D2CC14C01618CF62002E37CF /* SPDYProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYProtocol.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -216,10 +238,11 @@
064EFB1A16715C9F002F0AEC /* Supporting Files */,
069AA03816975B65005A72CA /* SPDYFrameCodecTest.m */,
0679F3CE186217FC006F122E /* SPDYOriginTest.m */,
25959A3E1937DE3900FC9731 /* SPDYSessionManagerTest.m */,
7774C2026A4DE9957D75F629 /* SPDYSessionTest.m */,
060C235D17CE9FCE000B4E9C /* SPDYStreamManagerTest.m */,
067EBFE617418F350029F16C /* SPDYStreamTest.m */,
25959A3E1937DE3900FC9731 /* SPDYSessionManagerTest.m */,
7774C803F0948788139CD2C1 /* SPDYServerPushTest.m */,
5C2229581952257800CAF160 /* SPDYURLRequestTest.m */,
);
path = SPDYUnitTests;
Expand All @@ -231,12 +254,16 @@
064EFB1B16715C9F002F0AEC /* SPDYUnitTests-Info.plist */,
064EFB1C16715C9F002F0AEC /* InfoPlist.strings */,
064EFB2216715C9F002F0AEC /* SPDYUnitTests-Prefix.pch */,
5CDA35B319DA32D900D3CAAA /* SPDYMockExtendedDelegate.h */,
5CDA35B619DA330700D3CAAA /* SPDYMockExtendedDelegate.m */,
064EFB2D16716389002F0AEC /* SPDYMockFrameDecoderDelegate.h */,
064EFB2E1671638A002F0AEC /* SPDYMockFrameDecoderDelegate.m */,
7774CD0A3295C8E314D6E3FF /* SPDYMockFrameEncoderDelegate.h */,
7774C69089A6978113F0C275 /* SPDYMockFrameEncoderDelegate.m */,
7774C7E1AF717FC36B7F15B6 /* SPDYSocket+SPDYSocketMock.h */,
7774C0ECD0C6E5D73FB38752 /* SPDYSocket+SPDYSocketMock.m */,
7774CFEA3D0DAF374D7C7654 /* SPDYMockSessionTestBase.h */,
7774C193AC525BC3A79F2853 /* SPDYMockSessionTestBase.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
Expand Down Expand Up @@ -332,6 +359,10 @@
061C8E9317C5954400D22083 /* SPDYStreamManager.m */,
06E7BF111823B74D004DB65D /* SPDYTLSTrustEvaluator.h */,
06290990169E497300E35A82 /* SPDYZLibCommon.h */,
7774C5A6A63982953028D4FF /* SPDYCacheStoragePolicy.h */,
7774CF2E562FD9368A064F75 /* SPDYCacheStoragePolicy.m */,
7774C111AB944B90A850CD41 /* SPDYCanonicalRequest.h */,
7774C8410DDDA27D92CB970F /* SPDYCanonicalRequest.m */,
);
path = SPDY;
sourceTree = "<group>";
Expand All @@ -349,6 +380,8 @@
0540DAAF19CB802600673796 /* SPDYTLSTrustEvaluator.h in Headers */,
0540DAAC19CB7FF900673796 /* SPDYError.h in Headers */,
0540DAA919CB7FEB00673796 /* SPDYCommonLogger.h in Headers */,
7774CAE01D37B13513AC3C60 /* SPDYCacheStoragePolicy.h in Headers */,
7774C69B8FAC0669C2D79CD5 /* SPDYCanonicalRequest.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -362,6 +395,8 @@
05C7CEBB19CB45820032D681 /* SPDYProtocol.h in Headers */,
05C7CEBC19CB458F0032D681 /* SPDYTLSTrustEvaluator.h in Headers */,
0540DAAA19CB7FEB00673796 /* SPDYCommonLogger.h in Headers */,
7774CB666CC0ECB10B6EB681 /* SPDYCacheStoragePolicy.h in Headers */,
7774C1F5D4F433D4563708C3 /* SPDYCanonicalRequest.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -397,7 +432,7 @@
name = SPDYUnitTests;
productName = SPDYUnitTests;
productReference = 064EFB1316715C9F002F0AEC /* SPDYUnitTests.octest */;
productType = "com.apple.product-type.bundle";
productType = "com.apple.product-type.bundle.ocunit-test";
};
0651EBE716F3F7C700CE44D2 /* SPDY.iphoneos */ = {
isa = PBXNativeTarget;
Expand Down Expand Up @@ -572,7 +607,12 @@
06B290CE1861018900540A03 /* SPDYOrigin.m in Sources */,
7774C1F1E544793907908882 /* SPDYMockFrameEncoderDelegate.m in Sources */,
7774CA1FA1F4A59CA0906BB7 /* SPDYSocket+SPDYSocketMock.m in Sources */,
5CDA35B819DA330700D3CAAA /* SPDYMockExtendedDelegate.m in Sources */,
7774CEA5AB5D1536D0CDCEA4 /* SPDYServerPushTest.m in Sources */,
7774C1318AB029C6BCEF84D6 /* SPDYSessionTest.m in Sources */,
7774C517D2EED111E723966D /* SPDYCacheStoragePolicy.m in Sources */,
7774CFD160EC2F7B74349445 /* SPDYCanonicalRequest.m in Sources */,
7774C2FBEA6973E90CAE7005 /* SPDYMockSessionTestBase.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -595,6 +635,8 @@
062EA643175D4CD3003BC1CE /* SPDYCommonLogger.m in Sources */,
061C8E9617C5954400D22083 /* SPDYStreamManager.m in Sources */,
06B290CF1861018A00540A03 /* SPDYOrigin.m in Sources */,
7774CE1EBC87500DBA40806D /* SPDYCacheStoragePolicy.m in Sources */,
7774C49E56F7198554B57FD3 /* SPDYCanonicalRequest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -617,6 +659,8 @@
062EA645175D4CD3003BC1CE /* SPDYCommonLogger.m in Sources */,
061C8E9817C5954400D22083 /* SPDYStreamManager.m in Sources */,
06B290D21861018A00540A03 /* SPDYOrigin.m in Sources */,
7774C6D327298AADBC891CF0 /* SPDYCacheStoragePolicy.m in Sources */,
7774C2528AAEAE6670822068 /* SPDYCanonicalRequest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
7 changes: 7 additions & 0 deletions SPDY/NSURLRequest+SPDYURLRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@

/**
A delegate will be scheduled on either an NSRunLoop or an NSOperationQueue, but not both.
IMPORTANT NOTE ABOUT DELEGATE RUNLOOPS / QUEUES:
If you call NSURLConnection's scheduleInRunLoop:forMode: or setDelegateQueue:, then you
MUST also call NSMutableURLRequest+SPDYURLRequest's setExtendedDelegate with the same runloop
or queue. This will ensure in-order delivery of SPDY extended delegate callbacks and
the URL loading system's callbacks.
*/
@property (nonatomic, readonly) NSRunLoop *SPDYDelegateRunLoop;
@property (nonatomic, readonly) NSString *SPDYDelegateRunLoopMode;
Expand Down
7 changes: 3 additions & 4 deletions SPDY/NSURLRequest+SPDYURLRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ - (void)setExtendedDelegate:(id<SPDYExtendedDelegate>)delegate inRunLoop:(NSRunL
[SPDYProtocol setProperty:delegate forKey:@"SPDYDelegate" inRequest:self];
[SPDYProtocol setProperty:runloop forKey:@"SPDYDelegateRunLoop" inRequest:self];
[SPDYProtocol setProperty:mode forKey:@"SPDYDelegateRunLoopMode" inRequest:self];
[SPDYProtocol setProperty:nil forKey:@"SPDYDelegateQueue" inRequest:self];
[SPDYProtocol removePropertyForKey:@"SPDYDelegateQueue" inRequest:self];
}

- (void)setExtendedDelegate:(id<SPDYExtendedDelegate>)delegate queue:(NSOperationQueue *)queue
Expand All @@ -267,10 +267,9 @@ - (void)setExtendedDelegate:(id<SPDYExtendedDelegate>)delegate queue:(NSOperatio
}

[SPDYProtocol setProperty:delegate forKey:@"SPDYDelegate" inRequest:self];
[SPDYProtocol setProperty:nil forKey:@"SPDYDelegateRunLoop" inRequest:self];
[SPDYProtocol setProperty:nil forKey:@"SPDYDelegateRunLoopMode" inRequest:self];
[SPDYProtocol removePropertyForKey:@"SPDYDelegateRunLoop" inRequest:self];
[SPDYProtocol removePropertyForKey:@"SPDYDelegateRunLoopMode" inRequest:self];
[SPDYProtocol setProperty:queue forKey:@"SPDYDelegateQueue" inRequest:self];
}


@end
26 changes: 26 additions & 0 deletions SPDY/SPDYCacheStoragePolicy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// SPDYCacheStoragePolicy.h
// SPDY
//
// Copyright (c) 2014 Twitter, Inc. All rights reserved.
// Licensed under the Apache License v2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Derived from code in Apple, Inc.'s CustomHTTPProtocol sample
// project, found as of this notice at
// https://developer.apple.com/LIBRARY/IOS/samplecode/CustomHTTPProtocol
//

#include <Foundation/Foundation.h>

/*! Determines the cache storage policy for a response.
* \details When we provide a response up to the client we need to tell the client whether
* the response is cacheable or not. The default HTTP/HTTPS protocol has a reasonable
* complex chunk of code to determine this, but we can't get at it. Thus, we have to
* reimplement it ourselves. This is split off into a separate file to emphasise that
* this is standard boilerplate that you probably don't need to look at.
* \param request The request that generated the response; must not be nil.
* \param response The response itself; must not be nil.
* \returns A cache storage policy to use.
*/
extern NSURLCacheStoragePolicy SPDYCacheStoragePolicy(NSURLRequest *request, NSHTTPURLResponse *response);
79 changes: 79 additions & 0 deletions SPDY/SPDYCacheStoragePolicy.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// SPDYCacheStoragePolicy.m
// SPDY
//
// Copyright (c) 2014 Twitter, Inc. All rights reserved.
// Licensed under the Apache License v2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Derived from code in Apple, Inc.'s CustomHTTPProtocol sample
// project, found as of this notice at
// https://developer.apple.com/LIBRARY/IOS/samplecode/CustomHTTPProtocol
//

#import "SPDYCacheStoragePolicy.h"

extern NSURLCacheStoragePolicy SPDYCacheStoragePolicy(NSURLRequest *request, NSHTTPURLResponse *response)
{
bool cacheable;
NSURLCacheStoragePolicy result;

// First determine if the request is cacheable based on its status code.

switch (response.statusCode) {
case 200:
case 203:

This comment has been minimized.

Copy link
@blakewatters

blakewatters Oct 2, 2014

Are 201 (Created) and 204 (No Content) not cacheable? 201 seems like it has the exact semantics as 200 and 204 may have value in the headers even though there’s no body.

This comment has been minimized.

Copy link
@kgoodier

kgoodier Oct 2, 2014

Author Owner

This code was taken from Apple's sample, which supposedly mimics what the HTTP/HTTPS protocol does (see comment in .h file). A 201 should be returned upon a resource getting created, which doesn't seem like a very cacheable action, though I suppose it depends on the request's verb (PUT vs POST) and intent. A 204 is less clear to me, I don't know why it was left off the list.

case 206:
case 301:
case 304:
case 404:
case 410:
cacheable = YES;
break;
default:
cacheable = NO;
break;
}

// If the response might be cacheable, look at the "Cache-Control" header in
// the response.

// IMPORTANT: We can't rely on -rangeOfString: returning valid results if the target
// string is nil, so we have to explicitly test for nil in the following two cases.

if (cacheable) {
NSString *responseHeader;

responseHeader = [response.allHeaderFields[@"cache-control"] lowercaseString];
if (responseHeader != nil && [responseHeader rangeOfString:@"no-store"].location != NSNotFound) {
cacheable = NO;
}
}

// If we still think it might be cacheable, look at the "Cache-Control" header in
// the request.

if (cacheable) {
NSString *requestHeader;

requestHeader = [request.allHTTPHeaderFields[@"cache-control"] lowercaseString];
if (requestHeader != nil &&
[requestHeader rangeOfString:@"no-store"].location != NSNotFound &&
[requestHeader rangeOfString:@"no-cache"].location != NSNotFound) {
cacheable = NO;
}
}

// Use the cacheable flag to determine the result.

if (cacheable) {
// Modern versions of iOS use file protection to protect the cache, and thus are
// happy to cache HTTPS on disk. Previous code here returned
// NSURLCacheStorageAllowedInMemoryOnly for https.
result = NSURLCacheStorageAllowed;
} else {
result = NSURLCacheStorageNotAllowed;
}

return result;
}
28 changes: 28 additions & 0 deletions SPDY/SPDYCanonicalRequest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// SPDYCanonicalRequest.h
// SPDY
//
// Copyright (c) 2014 Twitter, Inc. All rights reserved.
// Licensed under the Apache License v2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Derived from code in Apple, Inc.'s CustomHTTPProtocol sample
// project, found as of this notice at
// https://developer.apple.com/LIBRARY/IOS/samplecode/CustomHTTPProtocol
//

#import <Foundation/Foundation.h>

/*
The Foundation URL loading system needs to be able to canonicalize URL requests
for various reasons (for example, to look for cache hits). The default HTTP/HTTPS
protocol has a complex chunk of code to perform this function. Unfortunately
there's no way for third party code to access this. Instead, we have to reimplement
it all ourselves. This is split off into a separate file to emphasise that this
is standard boilerplate that you probably don't need to look at.
IMPORTANT: While you can take most of this code as read, you might want to tweak
the handling of the "Accept-Language" in the CanonicaliseHeaders routine.
*/

extern NSMutableURLRequest *SPDYCanonicalRequestForRequest(NSURLRequest *request);

5 comments on commit 46e8cbe

@plivesey
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is epic. I finally got SPDY push working with my server. Thanks for this code.

Is this as far as you got with server push? I noticed it doesn't look like its in your fork or twitter's repo.

@kgoodier
Copy link
Owner Author

@kgoodier kgoodier commented on 46e8cbe Oct 4, 2015 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@plivesey
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to get this into the official repo. Couple of things:

  1. This commit is pretty old. Does this branch have push enabled? Or is this the only commit that does this? It looks like this specific commit isn't in the current branch?
  2. I actually had some problems setting the push delegate on a mutable request (had to set it manually with the debugger). I'm confused how this works in the current project which seems to be doing similar things, but it looks like my mutable request is being changed:
// My code
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
    [req setExtendedDelegate:self queue:[NSOperationQueue mainQueue]];

    id task = [session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { /* ... */ }

I set a break point here and in + (BOOL)canInitWithRequest:(NSURLRequest *)request (I think this is the next time our code gets hit?

// My debugger
(lldb) po req
<NSMutableURLRequest: 0x7fe31a5c7110> { URL: http://localhost:8080 }

(lldb) po request
<NSURLRequest: 0x7fe31a633b80> { URL: http://localhost:8080 }

It looks like Apple is making a copy of the request we pass in? Or maybe only if its a mutable request to ensure immutability?
I'm running on iOS 9 and XCode 7 and maybe this is a new bug? Just wanted to let you know for when you test this.

Finally, let me know if you need any help with this. I'm really busy over the next month, but should have some time and much more soon.

@kgoodier
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh dear. I didn't check the commit you referenced since I was on a phone and just assumed it was the right one. It wasn't -- you found an abandoned approach. Apologies for misleading you. The extended delegate just didn't work right due to numerous issues, including the one you just mentioned. It also presented an unnecessarily complicated interface to the app.

I thought you were referencing this: #7. It's what I intend to merge into the main repo, after a rebase. It does not use any new delegates. Notifications back to the app are done via a global NSNotfication, and it is up to the app to filter the URL for ones of interest. CocoaSPDY only caches the pushed request for the lifetime of the parent request; the app must make a new NSURLRequest and "hook up" to the pushed request using normal Apple NSURL interfaces before the stream goes away.

Since you are both motivated and interested in this work, I'll get the rebase done and a pull request to the official repo posted soon, and you can try it out. Thanks for your help!

@plivesey
Copy link

@plivesey plivesey commented on 46e8cbe Oct 9, 2015 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.