Skip to content

Commit

Permalink
Support HTTP headers for source prop on <Image> components
Browse files Browse the repository at this point in the history
Allows developers to specify headers to include in the HTTP request
when fetching a remote image. For example, one might leverage this
when fetching an image from an endpoint that requires authentication:

```
<Image
  style={styles.logo}
  source={{
    uri: 'http://facebook.github.io/react/img/logo_og.png',
    headers: {
      Authorization: 'someAuthToken'
    }
  }}
/>
```

Note that the header values must be strings.

Works on iOS and Android.
  • Loading branch information
Adam Comella committed May 18, 2016
1 parent 79c5322 commit 32d7c77
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 14 deletions.
4 changes: 2 additions & 2 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ - (void)testImageLoading

NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil];

[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" headers:nil size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
Expand Down Expand Up @@ -79,7 +79,7 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority

NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil];

[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" headers:nil size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
Expand Down
6 changes: 6 additions & 0 deletions Libraries/Image/Image.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ var Image = React.createClass({
* `uri` is a string representing the resource identifier for the image, which
* could be an http address, a local file path, or a static image
* resource (which should be wrapped in the `require('./path/to/image.png')` function).
*
* `headers` is an object representing the HTTP headers to send along with the request
* for a remote image.
*/
source: PropTypes.oneOfType([
PropTypes.shape({
uri: PropTypes.string,
headers: PropTypes.objectOf(PropTypes.string),
}),
// Opaque type returned by require('./image.jpg')
PropTypes.number,
Expand Down Expand Up @@ -184,6 +188,7 @@ var Image = React.createClass({
style,
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd),
src: source.uri,
headers: source.headers,
loadingIndicatorSrc: loadingIndicatorSource ? loadingIndicatorSource.uri : null,
});

Expand Down Expand Up @@ -227,6 +232,7 @@ var styles = StyleSheet.create({
var cfg = {
nativeOnly: {
src: true,
headers: true,
loadingIndicatorSrc: true,
defaultImageSrc: true,
imageTag: true,
Expand Down
4 changes: 4 additions & 0 deletions Libraries/Image/Image.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ var Image = React.createClass({
* `uri` is a string representing the resource identifier for the image, which
* could be an http address, a local file path, or the name of a static image
* resource (which should be wrapped in the `require('./path/to/image.png')` function).
*
* `headers` is an object representing the HTTP headers to send along with the request
* for a remote image.
*/
source: PropTypes.oneOfType([
PropTypes.shape({
uri: PropTypes.string,
headers: PropTypes.objectOf(PropTypes.string),
}),
// Opaque type returned by require('./image.jpg')
PropTypes.number,
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Image/RCTImageLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* select the optimal dimensions for the loaded image.
*/
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
headers:(NSDictionary *)headers
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
Expand All @@ -74,6 +75,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* Loads an image without clipping the result to fit - used by RCTImageView.
*/
- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
headers:(NSDictionary *)headers
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
Expand Down
21 changes: 16 additions & 5 deletions Libraries/Image/RCTImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback
{
return [self loadImageWithTag:imageTag
headers:nil
size:CGSizeZero
scale:1
resizeMode:RCTResizeModeStretch
Expand Down Expand Up @@ -259,6 +260,7 @@ - (void)dequeueTasks
* the image, or retrieving metadata.
*/
- (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
headers:(NSDictionary *) headers
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
Expand Down Expand Up @@ -307,7 +309,7 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
}

// Find suitable image URL loader
NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
NSMutableURLRequest *request = [[RCTConvert NSURLRequest:imageTag] mutableCopy];
id<RCTImageURLLoader> loadHandler = [strongSelf imageURLLoaderForURL:request.URL];
if (loadHandler) {
cancelLoad = [loadHandler loadImageForURL:request.URL
Expand Down Expand Up @@ -363,9 +365,13 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag

// Add missing png extension
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
request = mutableRequest;
request.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
}

if (headers != nil){
for (NSString* key in headers) {
[request addValue:[headers objectForKey:key] forHTTPHeaderField:key];
}
}

// Check for cached response before reloading
Expand All @@ -383,7 +389,7 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
}

NSURL *redirectURL = [NSURL URLWithString: location];
request = [NSURLRequest requestWithURL: redirectURL];
request = [NSMutableURLRequest requestWithURL:redirectURL];
cachedResponse = [_URLCache cachedResponseForRequest:request];
continue;
}
Expand Down Expand Up @@ -447,13 +453,15 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
}

- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
headers:(NSDictionary *)headers
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
return [self loadImageWithoutClipping:imageTag
headers:headers
size:size
scale:scale
resizeMode:resizeMode
Expand All @@ -464,6 +472,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
}

- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
headers:(NSDictionary*)headers
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
Expand All @@ -489,6 +498,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
};

cancelLoad = [self loadImageOrDataWithTag:imageTag
headers:headers
size:size
scale:scale
resizeMode:resizeMode
Expand Down Expand Up @@ -631,6 +641,7 @@ - (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag
block:(void(^)(NSError *error, CGSize size))completionBlock
{
return [self loadImageOrDataWithTag:imageTag
headers:nil
size:CGSizeZero
scale:1
resizeMode:RCTResizeModeStretch
Expand Down
1 change: 1 addition & 0 deletions Libraries/Image/RCTImageView.m
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ - (void)reloadImage
CGFloat blurRadius = _blurRadius;
__weak RCTImageView *weakSelf = self;
_reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithoutClipping:_source.imageURL.absoluteString
headers:_source.headers
size:imageSize
scale:imageScale
resizeMode:(RCTResizeMode)self.contentMode
Expand Down
1 change: 1 addition & 0 deletions Libraries/Image/RCTShadowVirtualImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ - (void)didSetProps:(NSArray<NSString *> *)changedProps

__weak RCTShadowVirtualImage *weakSelf = self;
_cancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
headers:_source.headers
size:imageSize
scale:RCTScreenScale()
resizeMode:_resizeMode
Expand Down
2 changes: 2 additions & 0 deletions React/Base/RCTImageSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@interface RCTImageSource : NSObject

@property (nonatomic, strong, readonly) NSURL *imageURL;
@property (nonatomic, strong, readonly) NSDictionary *headers;
@property (nonatomic, assign, readonly) CGSize size;
@property (nonatomic, assign, readonly) CGFloat scale;

Expand All @@ -26,6 +27,7 @@
* size. Pass a scale of zero if you do not know or wish to specify the scale.
*/
- (instancetype)initWithURL:(NSURL *)url
headers:(NSDictionary *)headers
size:(CGSize)size
scale:(CGFloat)scale;

Expand Down
23 changes: 22 additions & 1 deletion React/Base/RCTImageSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ @interface RCTImageSource ()

@implementation RCTImageSource

- (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale
- (instancetype)initWithURL:(NSURL *)url headers:(NSDictionary *)headers size:(CGSize)size scale:(CGFloat)scale
{
if ((self = [super init])) {
_imageURL = url;
_headers = headers;
_size = size;
_scale = scale;
}
Expand All @@ -31,6 +32,7 @@ - (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale
- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale
{
RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:_imageURL
headers:_headers
size:size
scale:scale];
imageSource.packagerAsset = _packagerAsset;
Expand All @@ -57,13 +59,31 @@ + (RCTImageSource *)RCTImageSource:(id)json
}

NSURL *imageURL;
NSDictionary *headers = nil;
CGSize size = CGSizeZero;
CGFloat scale = 1.0;
BOOL packagerAsset = NO;
if ([json isKindOfClass:[NSDictionary class]]) {
if (!(imageURL = [self NSURL:RCTNilIfNull(json[@"uri"])])) {
return nil;
}
headers = [self NSDictionary:RCTNilIfNull(json[@"headers"])];
#if RCT_DEBUG
if (headers != nil) {
BOOL allHeadersAreStrings = YES;
for (NSString *key in headers) {
if (![[headers objectForKey:key] isKindOfClass:[NSString class]]) {
RCTLogError(@"Values of HTTP headers passed to an <Image> component must be"
" of type string. Value of header '%@' is not a string.", key);
allHeadersAreStrings = NO;
}
}
if (!allHeadersAreStrings) {
// Set headers to nil here to avoid crashing later.
headers = nil;
}
}
#endif
size = [self CGSize:json];
scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0;
packagerAsset = [self BOOL:json[@"__packager_asset"]];
Expand All @@ -75,6 +95,7 @@ + (RCTImageSource *)RCTImageSource:(id)json
}

RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL
headers:headers
size:size
scale:scale];
imageSource.packagerAsset = packagerAsset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.facebook.common.internal.AndroidPredicates;
import com.facebook.common.soloader.SoLoaderShim;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.backends.okhttp.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.listener.RequestListener;
Expand Down Expand Up @@ -99,8 +98,8 @@ private static ImagePipelineConfig getDefaultConfig(
}

OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient();
ImagePipelineConfig.Builder builder =
OkHttpImagePipelineConfigFactory.newBuilder(context.getApplicationContext(), okHttpClient);
ImagePipelineConfig.Builder builder = ImagePipelineConfig.newBuilder(context)
.setNetworkFetcher(new NetworkFetcher(okHttpClient));

builder
.setDownsampleEnabled(false)
Expand Down
Loading

0 comments on commit 32d7c77

Please sign in to comment.