Revision | 120 (tree) |
---|---|
Time | 2022-10-26 20:01:44 |
Author | ![]() |
Firebase Hosting の upload APIの呼び出しを並列化しました。遅いので…。
@@ -13,7 +13,7 @@ | ||
13 | 13 | public class FirebaseConfig extends UploadConfig { |
14 | 14 | |
15 | 15 | private String siteId; |
16 | - private Path secretKeyFilePath; | |
16 | + private Path serviceAccountKeyFilePath; | |
17 | 17 | |
18 | 18 | public FirebaseConfig() { |
19 | 19 | } |
@@ -22,8 +22,8 @@ | ||
22 | 22 | return this.siteId; |
23 | 23 | } |
24 | 24 | |
25 | - public Path getSecretKeyFilePath() { | |
26 | - return this.secretKeyFilePath; | |
25 | + public Path getServiceAccountKeyFilePath() { | |
26 | + return this.serviceAccountKeyFilePath; | |
27 | 27 | } |
28 | 28 | |
29 | 29 | private void initialize() { |
@@ -35,17 +35,17 @@ | ||
35 | 35 | } |
36 | 36 | this.siteId = s.trim(); |
37 | 37 | |
38 | - s = getValueAsString("secretKey"); | |
38 | + s = getValueAsString("serviceAccountKey"); | |
39 | 39 | if(s != null) { |
40 | 40 | String secretKey = s.replace('/', '\\'); |
41 | 41 | if(secretKey.length() >= 3 && secretKey.substring(1, 3).equals(":\\")) { |
42 | - this.secretKeyFilePath = Paths.get(secretKey).toAbsolutePath(); | |
42 | + this.serviceAccountKeyFilePath = Paths.get(secretKey).toAbsolutePath(); | |
43 | 43 | } else { |
44 | - Path p = getFolderPath("secretKey"); | |
45 | - this.secretKeyFilePath = p.resolve(secretKey).toAbsolutePath(); | |
44 | + Path p = getFolderPath("serviceAccountKey"); | |
45 | + this.serviceAccountKeyFilePath = p.resolve(secretKey).toAbsolutePath(); | |
46 | 46 | } |
47 | - if(!Files.exists(this.secretKeyFilePath)) { | |
48 | - throw new UncheckedIOException(new FileNotFoundException(this.secretKeyFilePath.toString())); | |
47 | + if(!Files.exists(this.serviceAccountKeyFilePath)) { | |
48 | + throw new UncheckedIOException(new FileNotFoundException(this.serviceAccountKeyFilePath.toString())); | |
49 | 49 | } |
50 | 50 | } |
51 | 51 | } |
@@ -25,6 +25,9 @@ | ||
25 | 25 | import java.util.LinkedHashMap; |
26 | 26 | import java.util.List; |
27 | 27 | import java.util.Map; |
28 | +import java.util.concurrent.CompletableFuture; | |
29 | +import java.util.concurrent.ExecutionException; | |
30 | +import java.util.concurrent.Executors; | |
28 | 31 | import java.util.stream.Stream; |
29 | 32 | import java.util.zip.Deflater; |
30 | 33 | import java.util.zip.GZIPOutputStream; |
@@ -44,7 +47,7 @@ | ||
44 | 47 | this.config = config; |
45 | 48 | } |
46 | 49 | |
47 | - public int upload(File localDirectory, ProgressObserver observer) throws IOException, InterruptedException, NoSuchAlgorithmException { | |
50 | + public int upload(File localDirectory, ProgressObserver observer) throws IOException, InterruptedException, NoSuchAlgorithmException, ExecutionException { | |
48 | 51 | this.observer = (observer != null) ? observer : ProgressObserver.EMPTY; |
49 | 52 | this.observer.setProgress(0.0); |
50 | 53 | this.observer.setText("アップロードの準備をしています…"); |
@@ -56,22 +59,21 @@ | ||
56 | 59 | throw new ToastMessage("Firebase Hosting", "siteId が指定されていません"); |
57 | 60 | } |
58 | 61 | |
59 | - Path secretKeyFilePath = config.getSecretKeyFilePath(); | |
60 | - if(secretKeyFilePath == null) { | |
61 | - throw new ToastMessage("Firebase Hosting", "secretKeyFilePath が指定されていません"); | |
62 | + Path serviceAccountKeyFilePath = config.getServiceAccountKeyFilePath(); | |
63 | + if(serviceAccountKeyFilePath == null) { | |
64 | + throw new ToastMessage("Firebase Hosting", "serviceAccountKey が指定されていません"); | |
62 | 65 | } |
63 | 66 | |
64 | 67 | Path input = localDirectory.toPath(); |
65 | 68 | Path output = MainApp.createTemporaryDirectory("upload-htdocs-gzipped", true); |
66 | 69 | |
70 | + String token = getAccessToken(serviceAccountKeyFilePath); | |
71 | + | |
67 | 72 | MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); |
68 | 73 | HttpClient client = HttpClient.newBuilder() |
69 | - .version(HttpClient.Version.HTTP_2) | |
70 | - .followRedirects(HttpClient.Redirect.NORMAL) | |
74 | + .executor(Executors.newFixedThreadPool(6)) | |
71 | 75 | .build(); |
72 | 76 | |
73 | - String token = getAccessToken(secretKeyFilePath); | |
74 | - | |
75 | 77 | String versionId = createVersionId(client, token, siteId); |
76 | 78 | |
77 | 79 | Map<Path, String> files = new LinkedHashMap<>(); |
@@ -109,16 +111,25 @@ | ||
109 | 111 | for(Map.Entry<Path, String> entry : files.entrySet()) { |
110 | 112 | map.put(entry.getValue(), entry.getKey()); |
111 | 113 | } |
114 | + uploadCount = populateFilesResult.uploadRequiredHashes.size(); | |
115 | + | |
116 | + @SuppressWarnings({"rawtypes", "unchecked"}) | |
117 | + CompletableFuture<HttpResponse<String>>[] futures = new CompletableFuture[uploadCount]; | |
118 | + int i = 0; | |
112 | 119 | for(String uploadRequiredhash : populateFilesResult.uploadRequiredHashes) { |
113 | 120 | Path file = map.get(uploadRequiredhash); |
114 | 121 | String url = populateFilesResult.uploadUrl + "/" + uploadRequiredhash; |
115 | 122 | |
116 | - this.observer.setProgress(++progress / (double)maxProgress); | |
117 | - this.observer.setText("/" + file.toString().replace('\\', '/')); | |
118 | - | |
119 | - upload(client, token, url, output.resolve(file)); | |
120 | - uploadCount++; | |
123 | + CompletableFuture<HttpResponse<String>> future = upload(client, token, url, output.resolve(file)); | |
124 | + future.thenAccept(response -> { | |
125 | + if(response.statusCode() == 200) { | |
126 | + this.observer.setProgress(++progress / (double)maxProgress); | |
127 | + this.observer.setText("/" + file.toString().replace('\\', '/')); | |
128 | + } | |
129 | + }); | |
130 | + futures[i++] = future; | |
121 | 131 | } |
132 | + CompletableFuture.allOf(futures).get(); | |
122 | 133 | } |
123 | 134 | |
124 | 135 | finalizeVersion(client, token, siteId, versionId); |
@@ -132,7 +143,7 @@ | ||
132 | 143 | try(InputStream in = Files.newInputStream(secretKeyJsonFile)) { |
133 | 144 | GoogleCredentials credential = GoogleCredentials |
134 | 145 | .fromStream(in) |
135 | - .createScoped("https://www.googleapis.com/auth/firebase"); | |
146 | + .createScoped("https://www.googleapis.com/auth/firebase.hosting"); | |
136 | 147 | |
137 | 148 | AccessToken token = credential.refreshAccessToken(); |
138 | 149 | return token.getTokenValue(); |
@@ -153,7 +164,7 @@ | ||
153 | 164 | "headers": [{ |
154 | 165 | "glob": "**", |
155 | 166 | "headers": { |
156 | - "cache-Control": "max-age=1800" | |
167 | + "Cache-Control": "max-age=1800" | |
157 | 168 | } |
158 | 169 | }] |
159 | 170 | } |
@@ -190,6 +201,7 @@ | ||
190 | 201 | } |
191 | 202 | if(files.size() > 0) { |
192 | 203 | sb.delete(sb.length() - 2, sb.length()); |
204 | + sb.append("\n"); | |
193 | 205 | } |
194 | 206 | sb.append(" }\n"); |
195 | 207 | sb.append("}\n"); |
@@ -212,7 +224,7 @@ | ||
212 | 224 | } |
213 | 225 | |
214 | 226 | |
215 | - private static void upload(HttpClient client, String token, String url, Path path) throws IOException, InterruptedException { | |
227 | + private static CompletableFuture<HttpResponse<String>> upload(HttpClient client, String token, String url, Path path) throws IOException, InterruptedException { | |
216 | 228 | HttpRequest request = HttpRequest.newBuilder(URI.create(url)) |
217 | 229 | .setHeader("Authorization", "Bearer " + token) |
218 | 230 | .setHeader("Content-Type", "application/octet-stream") |
@@ -219,11 +231,7 @@ | ||
219 | 231 | .POST(HttpRequest.BodyPublishers.ofFile(path)) |
220 | 232 | .build(); |
221 | 233 | |
222 | - HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |
223 | - | |
224 | - if(response.statusCode() != 200) { | |
225 | - throw new IOException(response + "\n" + response.body()); | |
226 | - } | |
234 | + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); | |
227 | 235 | } |
228 | 236 | |
229 | 237 |
@@ -269,7 +277,7 @@ | ||
269 | 277 | sha256.update(buf, 0, len); |
270 | 278 | } |
271 | 279 | } |
272 | - return String.format("%040x", new BigInteger(1, sha256.digest())); | |
280 | + return String.format("%064x", new BigInteger(1, sha256.digest())); | |
273 | 281 | } |
274 | 282 | |
275 | 283 |