Skip to content
This repository was archived by the owner on Dec 6, 2024. It is now read-only.

Commit adc6de1

Browse files
SynapticloopSynapticloop
authored andcommitted
added HEAD method
1 parent d41b20b commit adc6de1

15 files changed

Lines changed: 287 additions & 36 deletions

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
A Java API for the truly excellent backblaze B2 storage
2+
3+
Usable, however still a work in progress... (YMMV)
4+
5+
# Running the Tests
6+
7+
`gradled --info test`
8+
9+
Which will also print out the logging
10+
11+
if you do not have gradle installed, try:
12+
13+
`gradlew --info test`
14+
15+
**!!! IMPORTANT !!!**
16+
17+
You **MUST** have the following environment variables set:
18+
19+
```
20+
export B2_ACCOUNT_ID="your-account-id"
21+
export B2_APPLICATION_KEY="your-application-key"
22+
```
23+
24+
**WARNING:** These tests make API calls against your account which contribute to your call limits, which may lead to a cost.

src/main/java/synapticloop/b2/B2ApiClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import synapticloop.b2.request.B2DownloadFileByNameRequest;
1919
import synapticloop.b2.request.B2GetFileInfoRequest;
2020
import synapticloop.b2.request.B2GetUploadUrlRequest;
21+
import synapticloop.b2.request.B2HeadFileByIdRequest;
2122
import synapticloop.b2.request.B2ListBucketsRequest;
2223
import synapticloop.b2.request.B2ListFileNamesRequest;
2324
import synapticloop.b2.request.B2ListFileVersionsRequest;
@@ -382,6 +383,10 @@ public B2DownloadFileResponse downloadFileById(String fileId) throws B2ApiExcept
382383
return(new B2DownloadFileByIdRequest(getB2AuthorizeAccountResponse(), fileId).getResponse());
383384
}
384385

386+
public B2DownloadFileResponse headFileById(String fileId) throws B2ApiException {
387+
return(new B2HeadFileByIdRequest(getB2AuthorizeAccountResponse(), fileId).getResponse());
388+
}
389+
385390
private synchronized B2AuthorizeAccountResponse getB2AuthorizeAccountResponse() throws B2ApiException {
386391
if(null == b2AuthorizeAccountResponse) {
387392
b2AuthorizeAccountResponse = new B2AuthorizeAccountRequest(accountId, applicationKey).getResponse();

src/main/java/synapticloop/b2/request/B2AuthorizeAccountRequest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
* used for account-level operations, and a URL that should be used as the base
1111
* URL for subsequent API calls.</p>
1212
*
13+
* This is the interaction class for the <strong>b2_authorize_account</strong> api
14+
* calls, this was generated from the backblaze api documentation - which can be
15+
* found here:
1316
*
14-
* This is the interaction class for the <strong>b2_authorize_account</strong> api calls, this was
15-
* generated from the backblaze api documentation - which can be found here:
1617
* <a href="http://www.backblaze.com/b2/docs/b2_authorize_account.html">http://www.backblaze.com/b2/docs/b2_authorize_account.html</a>
1718
*
1819
* @author synapticloop

src/main/java/synapticloop/b2/request/B2CreateBucketRequest.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
* use a bucket with the same name. Buckets are assigned a unique bucketId
1313
* which is used when uploading, downloading, or deleting files.</p>
1414
*
15+
* This is the interaction class for the <strong>b2_create_bucket</strong> api
16+
* calls, this was generated from the backblaze api documentation - which can
17+
* be found here:
1518
*
16-
* This is the interaction class for the <strong>b2_create_bucket</strong> api calls, this was
17-
* generated from the backblaze api documentation - which can be found here:
1819
* <a href="http://www.backblaze.com/b2/docs/b2_create_bucket.html">http://www.backblaze.com/b2/docs/b2_create_bucket.html</a>
1920
*
2021
* @author synapticloop
@@ -27,8 +28,14 @@ public class B2CreateBucketRequest extends BaseB2Request {
2728
* Instantiate a new create bucket request
2829
*
2930
* @param b2AuthorizeAccountResponse the authorize account response
30-
* @param bucketName the name of the bucket to craete
31-
* @param bucketType the type of bucket to create
31+
* @param bucketName The name to give the new bucket. Bucket names must be
32+
* a minimum of 6 and a maximum of 50 characters long, and must be globally
33+
* unique; two different B2 accounts cannot have buckets with the same name.
34+
* Bucket names can consist of: letters, digits, and "-". Bucket names cannot
35+
* start with "b2-"; these are reserved for internal Backblaze use.
36+
* @param bucketType the type of bucket to create. Either "allPublic", meaning
37+
* that files in this bucket can be downloaded by anybody, or "allPrivate",
38+
* meaning that you need a bucket authorization token to download the files.
3239
*/
3340
public B2CreateBucketRequest(B2AuthorizeAccountResponse b2AuthorizeAccountResponse, String bucketName, BucketType bucketType) {
3441
super(b2AuthorizeAccountResponse);

src/main/java/synapticloop/b2/request/B2DeleteBucketRequest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
/**
88
* <p>Deletes the bucket specified. Only buckets that contain no version of any files can be deleted.</p>
99
*
10+
* This is the interaction class for the <strong>b2_delete_bucket</strong> api
11+
* calls, this was generated from the backblaze api documentation - which can
12+
* be found here:
1013
*
11-
* This is the interaction class for the <strong>b2_delete_bucket</strong> api calls, this was
12-
* generated from the backblaze api documentation - which can be found here:
1314
* <a href="http://www.backblaze.com/b2/docs/b2_delete_bucket.html">http://www.backblaze.com/b2/docs/b2_delete_bucket.html</a>
1415
*
1516
* @author synapticloop

src/main/java/synapticloop/b2/request/B2DeleteFileVersionRequest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public class B2DeleteFileVersionRequest extends BaseB2Request {
2525
*
2626
* @param b2AuthorizeAccountResponse the authorize account response
2727
* @param fileName the name of the file to delete
28-
* @param fileId the id of the file to delete
28+
* @param fileId The ID of the file, as returned by {@link B2UploadFileRequest},
29+
* {@link B2ListFileNamesRequest}, or {@link B2ListFileVersionsRequest}..
2930
*/
3031
public B2DeleteFileVersionRequest(B2AuthorizeAccountResponse b2AuthorizeAccountResponse, String fileName, String fileId) {
3132
super(b2AuthorizeAccountResponse);
@@ -35,6 +36,13 @@ public B2DeleteFileVersionRequest(B2AuthorizeAccountResponse b2AuthorizeAccountR
3536
stringData.put(KEY_FILE_ID, fileId);
3637
}
3738

39+
/**
40+
* Return the http response for the call
41+
*
42+
* @return the delete file version response
43+
*
44+
* @throws B2ApiException if there was an error with the call
45+
*/
3846
public B2DeleteFileVersionResponse getResponse() throws B2ApiException {
3947
return(new B2DeleteFileVersionResponse(executePost()));
4048
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package synapticloop.b2.request;
2+
3+
import synapticloop.b2.exception.B2ApiException;
4+
import synapticloop.b2.response.B2AuthorizeAccountResponse;
5+
import synapticloop.b2.response.B2DownloadFileResponse;
6+
7+
/**
8+
* <p>Downloads one file from B2.</p>
9+
*
10+
* <p>The response contains the following headers, which contain the same information they did when the file was uploaded:</p>
11+
*
12+
*
13+
* This is the interaction class for the <strong>b2_download_file_by_id</strong>
14+
* api calls, this was generated from the backblaze api documentation - which
15+
* can be found here:
16+
*
17+
* <a href="http://www.backblaze.com/b2/docs/b2_download_file_by_id.html">http://www.backblaze.com/b2/docs/b2_download_file_by_id.html</a>
18+
*
19+
* @author synapticloop
20+
*/
21+
22+
public class B2HeadFileByIdRequest extends BaseB2Request {
23+
private static final String B2_DOWNLOAD_FILE_BY_ID = BASE_API_VERSION + "b2_download_file_by_id";
24+
25+
public B2HeadFileByIdRequest(B2AuthorizeAccountResponse b2AuthorizeAccountResponse, String fileId) {
26+
super(b2AuthorizeAccountResponse);
27+
url = b2AuthorizeAccountResponse.getDownloadUrl() + B2_DOWNLOAD_FILE_BY_ID;
28+
parameters.put(KEY_FILE_ID, fileId);
29+
}
30+
31+
/**
32+
* R
33+
*
34+
* @return
35+
* @throws B2ApiException
36+
*/
37+
public B2DownloadFileResponse getResponse() throws B2ApiException {
38+
return(new B2DownloadFileResponse(executeHead()));
39+
}
40+
}

src/main/java/synapticloop/b2/request/B2UploadFileRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public B2UploadFileRequest(B2AuthorizeAccountResponse b2AuthorizeAccountResponse
4545
super(b2AuthorizeAccountResponse);
4646
this.fileName = fileName;
4747
this.url = b2GetUploadUrlResponse.getUploadUrl();
48+
headers.put(REQUEST_PROPERTY_AUTHORIZATION, b2GetUploadUrlResponse.getAuthorizationToken());
4849
this.file = file;
4950
this.mimeType = mimeType;
5051

src/main/java/synapticloop/b2/request/BaseB2Request.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.apache.http.client.methods.CloseableHttpResponse;
1313
import org.apache.http.client.methods.HttpGet;
14+
import org.apache.http.client.methods.HttpHead;
1415
import org.apache.http.client.methods.HttpPost;
1516
import org.apache.http.client.methods.HttpRequestBase;
1617
import org.apache.http.client.utils.URIBuilder;
@@ -42,7 +43,7 @@ public class BaseB2Request {
4243
protected static final String HEADER_CONTENT_TYPE = "Content-Type";
4344
protected static final String HEADER_X_BZ_CONTENT_SHA1 = "X-Bz-Content-Sha1";
4445
protected static final String HEADER_X_BZ_FILE_NAME = "X-Bz-File-Name";
45-
protected static final String HEADER_X_BZ_INFO_PREFIX = "X-Bz-Info-";
46+
protected static final String HEADER_X_BZ_INFO_PREFIX = "x-bz-info-";
4647

4748
protected static final String KEY_ACCOUNT_ID = "accountId";
4849
protected static final String KEY_BUCKET_ID = "bucketId";
@@ -116,6 +117,38 @@ protected String convertPostData() throws B2ApiException {
116117
return(jsonObject.toString());
117118
}
118119

120+
protected CloseableHttpResponse executeHead() throws B2ApiException {
121+
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
122+
123+
try {
124+
URI uri = new URIBuilder(url).build();
125+
LOG.debug("HEAD request to URL '{}'", url);
126+
127+
Iterator<String> iterator = parameters.keySet().iterator();
128+
while (iterator.hasNext()) {
129+
String key = (String) iterator.next();
130+
// @TODO - this is kind of wasteful - refactor
131+
uri = new URIBuilder(uri).addParameter(key, parameters.get(key)).build();
132+
}
133+
134+
HttpHead httpHead = new HttpHead(uri);
135+
setHeaders(httpHead);
136+
137+
CloseableHttpResponse httpResponse = closeableHttpClient.execute(httpHead);
138+
int statusCode = httpResponse.getStatusLine().getStatusCode();
139+
140+
LOG.debug("Received status code of:{}, for HEAD request to url '{}'", statusCode, uri);
141+
142+
if(statusCode != 200) {
143+
throw new B2ApiException("Received status code of " + statusCode + " for request.");
144+
} else {
145+
return(httpResponse);
146+
}
147+
} catch (IOException | URISyntaxException ex) {
148+
throw new B2ApiException(ex);
149+
}
150+
}
151+
119152
protected String executeGet() throws B2ApiException {
120153
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
121154
HttpGet httpGet = new HttpGet(url);
@@ -141,6 +174,7 @@ protected String executeGet() throws B2ApiException {
141174
}
142175
}
143176

177+
144178
protected CloseableHttpResponse executeGetWithData() throws B2ApiException {
145179
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
146180

src/main/java/synapticloop/b2/response/B2DownloadFileResponse.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,44 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.util.HashMap;
6+
import java.util.HashSet;
67
import java.util.Map;
8+
import java.util.Set;
79

810
import org.apache.http.Header;
911
import org.apache.http.client.methods.CloseableHttpResponse;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
1014

1115
import synapticloop.b2.exception.B2ApiException;
1216

1317
public class B2DownloadFileResponse extends BaseB2Response {
18+
private static final Logger LOG = LoggerFactory.getLogger(B2DownloadFileResponse.class);
19+
1420
private static final int HEADER_CONTENT_LENGTH = 0;
1521
private static final int HEADER_CONTENT_TYPE = 1;
1622
private static final int HEADER_X_BZ_FILE_ID = 2;
1723
private static final int HEADER_X_BZ_FILE_NAME = 3;
1824
private static final int HEADER_X_BZ_CONTENT_SHA1 = 4;
1925

26+
// headers are all lowercase for lookup
2027
private static final Map<String, Integer> headerLookup = new HashMap<String, Integer>();
2128
static {
22-
headerLookup.put("Content-Length", HEADER_CONTENT_LENGTH);
23-
headerLookup.put("Content-Type", HEADER_CONTENT_TYPE);
24-
headerLookup.put("X-Bz-File-Id", HEADER_X_BZ_FILE_ID);
25-
headerLookup.put("X-Bz-File-Name", HEADER_X_BZ_FILE_NAME);
26-
headerLookup.put("X-Bz-Content-Sha1", HEADER_X_BZ_CONTENT_SHA1);
29+
headerLookup.put("content-length", HEADER_CONTENT_LENGTH);
30+
headerLookup.put("content-type", HEADER_CONTENT_TYPE);
31+
headerLookup.put("x-bz-file-id", HEADER_X_BZ_FILE_ID);
32+
headerLookup.put("x-bz-file-name", HEADER_X_BZ_FILE_NAME);
33+
headerLookup.put("x-bz-content-sha1", HEADER_X_BZ_CONTENT_SHA1);
34+
}
35+
36+
private static final Set<String> ignoredHeaders = new HashSet<String>();
37+
static {
38+
ignoredHeaders.add("server");
39+
ignoredHeaders.add("x-content-type-options");
40+
ignoredHeaders.add("x-xss-protection");
41+
ignoredHeaders.add("x-frame-options");
42+
ignoredHeaders.add("cache-control");
43+
ignoredHeaders.add("date");
2744
}
2845

2946
private InputStream content = null;
@@ -37,7 +54,10 @@ public class B2DownloadFileResponse extends BaseB2Response {
3754

3855
public B2DownloadFileResponse(CloseableHttpResponse closeableHttpResponse) throws B2ApiException {
3956
try {
40-
content = closeableHttpResponse.getEntity().getContent();
57+
// HEAD responses do not have an entity
58+
if(null != closeableHttpResponse.getEntity()) {
59+
content = closeableHttpResponse.getEntity().getContent();
60+
}
4161
parseHeaders(closeableHttpResponse);
4262

4363
} catch (IllegalStateException | IOException ex) {
@@ -51,8 +71,8 @@ private void parseHeaders(CloseableHttpResponse closeableHttpResponse) throws B2
5171
for (Header header : allHeaders) {
5272
String headerName = header.getName();
5373
String headerValue = header.getValue();
54-
if(headerLookup.containsKey(headerName)) {
55-
switch (headerLookup.get(headerName)) {
74+
if(headerLookup.containsKey(headerName.toLowerCase())) {
75+
switch (headerLookup.get(headerName.toLowerCase())) {
5676
case HEADER_CONTENT_LENGTH:
5777
contentLength = Integer.parseInt(headerValue);
5878
break;
@@ -76,13 +96,18 @@ private void parseHeaders(CloseableHttpResponse closeableHttpResponse) throws B2
7696
// headers
7797
if(headerName.startsWith(HEADER_X_BZ_INFO_PREFIX)) {
7898
fileInfo.put(headerName.substring(HEADER_X_BZ_INFO_PREFIX.length()), headerValue);
99+
} else {
100+
if(!ignoredHeaders.contains(headerName.toLowerCase())) {
101+
LOG.warn("Found a header named '{}' with value '{}', that was not mapped", headerName, headerValue);
102+
}
79103
}
80104
}
81105
}
82106
}
83107

84108
/**
85-
* Get the content of the downloaded file
109+
* Get the content of the downloaded file, if this was a HEAD request, then
110+
* this will return null.
86111
*
87112
* @return the downloaded file
88113
*/

0 commit comments

Comments
 (0)