気がするんだけど本当?
同じ画像をアップロードした際、iOSではcontent-type
がapplication/octet-stream
になって、Androidではimage/jpeg
になった。なぜiOSで推測されないのか気になるから追ってみました。ソースを見てきちんとそうなることを確認したかったのです。
ただ先に言っておくとiOSで推測されないのは確かなので、解決法としては自分でcontent-typeをつけるのがメジャーになるとは思います。この記事はcontent-typeがつかない問題の解決法を教えるのではなく、ソースを追って、確かに推測されてない!!!ってなるための記事です。
Flutterのdartからネイティブソースを呼び出す流れに詳しくない人は、これを読むと こんな感じで呼び出してるんだ〜〜へーーってなれるかも
iOS側の呼び出しを追っていく
dartの StorageReference#putFile
の中の _StorageFileUploadTask
の中から。たまにオーバーロードしてるメソッドとかは飛ばしてます。
ほないくで
firebase_storage plugin dart - invokeMethod
@override
Future<dynamic> _platformStart() {
return FirebaseStorage.channel.invokeMethod( // <- method channelを使って
'StorageReference#putFile', // <- StorageReference#putFile を蹴りたい
<String, dynamic>{
'app': _firebaseStorage.app?.name,
'bucket': _firebaseStorage.storageBucket,
'filename': _file.absolute.path,
'path': _ref.path,
'metadata':
_metadata == null ? null : _buildMetadataUploadMap(_metadata),
},
);
}
firebase_storage plugin - create FlutterMethodChannel
FlutterMethodChannel *channel = // 蹴るためにFlutterMethodChannelを作っておく
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_storage"
binaryMessenger:[registrar messenger]];
FLTFirebaseStoragePlugin *instance = [[FLTFirebaseStoragePlugin alloc] init];
instance.channel = channel;
[registrar addMethodCallDelegate:instance channel:channel];
}
firebase_storage plugin - handle method call
} else if ([@"StorageReference#putFile" isEqualToString:call.method]) {
[self putFile:call result:result]; // self.putFileを呼び出す
}
firebase_storage plugin - put file
- (void)putFile:(FlutterMethodCall *)call result:(FlutterResult)result {
NSData *data = [NSData dataWithContentsOfFile:call.arguments[@"filename"]];
[self put:data call:call result:result]; // self.putを呼び出す
}
firebase_storage plugin - put
- (void)put:(NSData *)data call:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *path = call.arguments[@"path"];
NSDictionary *metadataDictionary = call.arguments[@"metadata"];
FIRStorageMetadata *metadata;
if (![metadataDictionary isEqual:[NSNull null]]) {
metadata = [self buildMetadataFromDictionary:metadataDictionary];
}
FIRStorageReference *fileRef = [storage.reference child:path];
FIRStorageUploadTask *uploadTask = [fileRef putData:data metadata:metadata]; // FIRStorageReference#putData を呼び出す
// ...some procedure...
}
firebase-ios-sdk - put data
ここから、flutterのプラグインではなく、firebase-ios-sdk側のソースになります
- (FIRStorageUploadTask *)putData:(NSData *)uploadData
metadata:(nullable FIRStorageMetadata *)metadata
completion:(nullable FIRStorageVoidMetadataError)completion {
if (!metadata) {
metadata = [[FIRStorageMetadata alloc] init];
}
metadata.path = _path.object;
metadata.name = [_path.object lastPathComponent];
FIRStorageUploadTask *task =
[[FIRStorageUploadTask alloc] initWithReference:self // FIRStorageUploadTaskを初期化
fetcherService:_storage.fetcherServiceForApp
dispatchQueue:_storage.dispatchQueue
data:uploadData // 引数がfileじゃなくてdata
metadata:metadata];
// ...some procedure...
}
元凶:firebase-ios-sdk - init FIRStorageUploadTask with data
- (instancetype)initWithReference:(FIRStorageReference *)reference
fetcherService:(GTMSessionFetcherService *)service
dispatchQueue:(dispatch_queue_t)queue
data:(NSData *)uploadData
metadata:(FIRStorageMetadata *)metadata {
self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
if (self) {
_uploadMetadata = [metadata copy];
_uploadData = [uploadData copy];
_progress = [NSProgress progressWithTotalUnitCount:[_uploadData length]];
if (!_uploadMetadata.contentType) {
_uploadMetadata.contentType = @"application/octet-stream"; // あー!!!!!!!!!!!
}
}
return self;
}
ちなみに
FIRStorageReference#putData
じゃなくてputFile
を呼び出してると以下のソースにたどり着き、きちんとcontent-type
がつく
- (instancetype)initWithReference:(FIRStorageReference *)reference
fetcherService:(GTMSessionFetcherService *)service
dispatchQueue:(dispatch_queue_t)queue
file:(NSURL *)fileURL // <- dataじゃなくてfile
metadata:(FIRStorageMetadata *)metadata {
self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
if (self) {
_uploadMetadata = [metadata copy];
_fileURL = [fileURL copy];
_progress = [NSProgress progressWithTotalUnitCount:0];
// イケメン
NSString *mimeType = [FIRStorageUtils MIMETypeForExtension:[_fileURL pathExtension]];
if (!_uploadMetadata.contentType) {
_uploadMetadata.contentType = mimeType ?: @"application/octet-stream";
}
}
return self;
}
firebase docでは
putFile: メソッドは NSURL ファイル名拡張子からコンテンツ タイプを自動的に推測します
って書いてるが、flutterのplugin自体がそっち呼んでないから推測しないんだなぁ〜
Androidは?
力尽きた
雑記
firebase-ios-sdkはTags見たらそれっぽいバージョンあったからそのURL引っ張ってきたんだけど、flutterの方はそれっぽいバージョン見つけられなくてmasterになっちゃってる。バージョンとかでTag切られてないのかな?よくわかんないっす