-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: auto content-type on blob creation #338
Changes from 4 commits
9795972
a5da742
6320364
3cc79cb
15fe56c
6b7f98b
6b14a3e
736ed2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,8 @@ | |
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.math.BigInteger; | ||
import java.net.FileNameMap; | ||
import java.net.URLConnection; | ||
import java.util.ArrayList; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
|
@@ -98,6 +100,7 @@ public class HttpStorageRpc implements StorageRpc { | |
private final HttpRequestInitializer batchRequestInitializer; | ||
|
||
private static final long MEGABYTE = 1024L * 1024L; | ||
private static final FileNameMap FILE_NAME_MAP = URLConnection.getFileNameMap(); | ||
|
||
public HttpStorageRpc(StorageOptions options) { | ||
HttpTransportOptions transportOptions = (HttpTransportOptions) options.getTransportOptions(); | ||
|
@@ -286,7 +289,7 @@ public StorageObject create( | |
.insert( | ||
storageObject.getBucket(), | ||
storageObject, | ||
new InputStreamContent(storageObject.getContentType(), content)); | ||
new InputStreamContent(detectContentType(storageObject), content)); | ||
insert.getMediaHttpUploader().setDirectUploadEnabled(true); | ||
Boolean disableGzipContent = Option.IF_DISABLE_GZIP_CONTENT.getBoolean(options); | ||
if (disableGzipContent != null) { | ||
|
@@ -372,6 +375,16 @@ public Tuple<String, Iterable<StorageObject>> list(final String bucket, Map<Opti | |
} | ||
} | ||
|
||
private static String detectContentType(StorageObject object) { | ||
String contentType = object.getContentType(); | ||
if (contentType != null) { | ||
return contentType; | ||
} | ||
return firstNonNull( | ||
FILE_NAME_MAP.getContentTypeFor(object.getName().toLowerCase()), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. toLowerCase always should have a locale There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
"application/octet-stream"); | ||
} | ||
|
||
private static Function<String, StorageObject> objectFromPrefix(final String bucket) { | ||
return new Function<String, StorageObject>() { | ||
@Override | ||
|
@@ -811,9 +824,7 @@ public String open(StorageObject object, Map<Option, ?> options) { | |
HttpRequest httpRequest = | ||
requestFactory.buildPostRequest(url, new JsonHttpContent(jsonFactory, object)); | ||
HttpHeaders requestHeaders = httpRequest.getHeaders(); | ||
requestHeaders.set( | ||
"X-Upload-Content-Type", | ||
firstNonNull(object.getContentType(), "application/octet-stream")); | ||
requestHeaders.set("X-Upload-Content-Type", detectContentType(object)); | ||
String key = Option.CUSTOMER_SUPPLIED_KEY.getString(options); | ||
if (key != null) { | ||
BaseEncoding base64 = BaseEncoding.base64(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3273,4 +3273,45 @@ public void testBlobReload() throws Exception { | |
updated.delete(); | ||
assertNull(updated.reload()); | ||
} | ||
|
||
private Blob createBlob(String method, BlobInfo blobInfo) throws IOException { | ||
switch (method) { | ||
case "create": | ||
return storage.create(blobInfo); | ||
case "writer": | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the braces here? |
||
storage.writer(blobInfo).close(); | ||
return storage.get(BlobId.of(blobInfo.getBucket(), blobInfo.getName())); | ||
} | ||
default: | ||
throw new IllegalArgumentException("Unknown method " + method); | ||
} | ||
} | ||
|
||
private void testAutoContentType(String method) throws IOException { | ||
String[] names = {"file1.txt", "dir with spaces/Pic.Jpg", "no_extension"}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way to infer the type based on bytes rather than file extension? That's how Golang does it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not always possible. In case of resumeable upload a blob is created first and the content is available later:
I did an experiment: gsutil: text/plain To achieve consistent behavior across all the clients it should be implemented on the server-side at the moment when the upload is finished. Until this feature is implemented I suggest type detecting based on file extension, as it has been done recently in nodejs (googleapis/nodejs-storage#1190) |
||
String[] types = {"text/plain", "image/jpeg", "application/octet-stream"}; | ||
Blob blob = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can push this into the for loop |
||
for (int i = 0; i < names.length; i++) { | ||
BlobId blobId = BlobId.of(BUCKET, names[i]); | ||
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); | ||
blob = createBlob(method, blobInfo); | ||
assertEquals(types[i], blob.getContentType()); | ||
} | ||
String customType = "custom/type"; | ||
BlobId blobId = BlobId.of(BUCKET, names[0]); | ||
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(customType).build(); | ||
blob = createBlob(method, blobInfo); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't reuse local variable. There are two separate blob objects here that coincidentally share a variable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a reason to reuse the variable but after rewriting the test that reason is not actual anymore. Thanks for catching that. |
||
assertEquals(customType, blob.getContentType()); | ||
} | ||
|
||
@Test | ||
public void testAutoContentTypeCreate() throws IOException { | ||
testAutoContentType("create"); | ||
} | ||
|
||
@Test | ||
public void testAutoContentTypeWriter() throws IOException { | ||
testAutoContentType("writer"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an optional feature that's disabled by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about making this feature optional, but I didn't find an appropriate place to store this setting. It could be a property of a project/client/bucket/object.
I only found: https://cloud.google.com/storage/docs/metadata#content-type
which states:
If the Content-Type is not specified by the uploader and cannot be determined, it is set to application/octet-stream or application/x-www-form-urlencoded, depending on how you uploaded the object.
If I read this correctly, the Storage should attempt to determine the content type if not explicitly set, so this feature should be on by default.
BTW, right now the content type of blobs created with Storage.create() methods is
null
, but should beapplication/octet-stream
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storage doesn't provide contentType detection in the API.
If contentType is set at creation then
application/octet-stream
is used.Please make this optional otherwise it's a behavior change not originally intended on uploads with the Java client.