-
Notifications
You must be signed in to change notification settings - Fork 323
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
Remove akka from runtime #8953
Remove akka from runtime #8953
Changes from 26 commits
0b38e89
9f8832f
0c9719b
3688cc8
bd71fd9
b17f86b
8444663
0ef2ffd
506c582
dc1e582
7d79c02
ed8a567
28173fb
4163d29
a681dc2
b845057
3e6f3a4
b9cdeea
0bab2bb
c746448
62c1414
ec44437
5eaef2e
8c63995
553a785
212070d
f3f3606
4f1e54c
0d30555
0e2b1d6
c5cc2dc
743e167
6a9fff2
ba0a69d
148a64d
9965c4c
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 |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.enso.runtime.test; | ||
|
||
import static org.junit.Assert.fail; | ||
|
||
import org.junit.Test; | ||
|
||
public class DependenciesTest { | ||
@Test | ||
public void noAkkaDependencies() { | ||
try { | ||
Class.forName("akka.actor.ActorSystem$"); | ||
fail("akka.actor.ActorSystem should not be on the classpath"); | ||
} catch (ClassNotFoundException e) { | ||
// expected | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package org.enso.downloader.http; | ||
|
||
import java.io.IOException; | ||
import java.net.http.HttpRequest; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
/** | ||
* This utility class adds support for {@code multipart/form-data} content-type as there is no such | ||
* support in {@code java.net.http} JDK. | ||
* | ||
* <p>Inspired by <a href="https://stackoverflow.com/a/56482187/4816269">SO</a>. | ||
*/ | ||
public class FilesMultipartBodyPublisher { | ||
public static HttpRequest.BodyPublisher ofMimeMultipartData( | ||
Comment on lines
+12
to
+19
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 don't like us implementing such utilities ourselves instead of using existing well-tested solutions. This works nicely for simple cases, but how do we guarantee that it will work well in general? Should we try testing it as well as an existing solution would? That adds a lot of maintenance burden on us.
I see it is generated as a UUID. I guess it is extremely unlikely that the same UUID would appear inside of the contents of one of the parts submitted as part of this form, but it is not 100% guaranteed. I'm a bit nitpicky here as indeed it is going to be very unlikely to run into this kind of collision. But was this even considered when this code was written? This is a seemingly simple operation but it may raise many uncertainities like the example above. IMO relying on an existing solution is better as it does not put the maintenance burden of ensuring this solution is bug free, on us. 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. The Implementing a very simple handling of |
||
Collection<Path> files, String boundary) throws IOException { | ||
// Result request body | ||
List<byte[]> byteArrays = new ArrayList<>(); | ||
|
||
// Separator with boundary | ||
byte[] separator = | ||
("--" + boundary + "\r\nContent-Disposition: form-data; name=\"files\";") | ||
.getBytes(StandardCharsets.UTF_8); | ||
|
||
for (Path path : files) { | ||
// Opening boundary | ||
byteArrays.add(separator); | ||
|
||
// If value is type of Path (file) append content type with file name and file binaries, | ||
// otherwise simply append key=value | ||
String mimeType = Files.probeContentType(path); | ||
byteArrays.add( | ||
(" filename=\"" + path.getFileName() + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n") | ||
.getBytes(StandardCharsets.UTF_8)); | ||
byteArrays.add(Files.readAllBytes(path)); | ||
byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8)); | ||
} | ||
|
||
// Closing boundary | ||
byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8)); | ||
|
||
return HttpRequest.BodyPublishers.ofByteArrays(byteArrays); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package org.enso.downloader.http; | ||
|
||
import java.io.IOException; | ||
import java.net.http.HttpResponse; | ||
import java.net.http.HttpResponse.BodySubscriber; | ||
import java.net.http.HttpResponse.ResponseInfo; | ||
import java.nio.ByteBuffer; | ||
import java.nio.channels.WritableByteChannel; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.StandardOpenOption; | ||
import java.util.List; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.concurrent.Flow; | ||
import org.enso.cli.task.TaskProgressImplementation; | ||
import scala.Option; | ||
import scala.Some; | ||
|
||
/** A {@link HttpResponse} body handler for {@link Path} that keeps track of the progress. */ | ||
class PathProgressBodyHandler implements HttpResponse.BodyHandler<Path> { | ||
private final Path destination; | ||
private final TaskProgressImplementation<Path> progress; | ||
private Long total; | ||
|
||
private PathProgressBodyHandler( | ||
Path destination, TaskProgressImplementation<Path> progress, Long total) { | ||
this.destination = destination; | ||
this.progress = progress; | ||
this.total = total; | ||
} | ||
|
||
static PathProgressBodyHandler of( | ||
Path destination, TaskProgressImplementation<Path> progress, Long sizeHint) { | ||
return new PathProgressBodyHandler(destination, progress, sizeHint); | ||
} | ||
|
||
@Override | ||
public BodySubscriber<Path> apply(ResponseInfo responseInfo) { | ||
if (total == null) { | ||
var reportedLenOpt = responseInfo.headers().firstValueAsLong("Content-Length"); | ||
if (reportedLenOpt.isPresent()) { | ||
total = reportedLenOpt.getAsLong(); | ||
} | ||
} | ||
if (total != null) { | ||
progress.reportProgress(0, Some.apply(total)); | ||
} else { | ||
progress.reportProgress(0, Option.empty()); | ||
} | ||
WritableByteChannel destChannel; | ||
try { | ||
destChannel = | ||
Files.newByteChannel(destination, StandardOpenOption.CREATE, StandardOpenOption.WRITE); | ||
} catch (IOException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
return new ProgressSubscriber(destChannel); | ||
} | ||
|
||
private class ProgressSubscriber implements BodySubscriber<Path> { | ||
private Flow.Subscription subscription; | ||
private final WritableByteChannel destChannel; | ||
private final CompletableFuture<Path> result = new CompletableFuture<>(); | ||
|
||
ProgressSubscriber(WritableByteChannel destChannel) { | ||
this.destChannel = destChannel; | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Flow.Subscription subscription) { | ||
this.subscription = subscription; | ||
this.subscription.request(1); | ||
} | ||
|
||
@Override | ||
public void onNext(List<ByteBuffer> items) { | ||
try { | ||
for (ByteBuffer item : items) { | ||
var len = item.remaining(); | ||
progress.reportProgress(len, total == null ? Option.empty() : Some.apply(total)); | ||
destChannel.write(item); | ||
} | ||
subscription.request(1); | ||
} catch (IOException e) { | ||
subscription.cancel(); | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void onError(Throwable throwable) { | ||
try { | ||
destChannel.close(); | ||
} catch (IOException e) { | ||
throwable.addSuppressed(e); | ||
} | ||
result.completeExceptionally(throwable); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
try { | ||
destChannel.close(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
result.complete(destination); | ||
} | ||
|
||
@Override | ||
public CompletionStage<Path> getBody() { | ||
return CompletableFuture.completedFuture(destination); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package org.enso.downloader.http; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.net.http.HttpResponse; | ||
import java.net.http.HttpResponse.BodySubscriber; | ||
import java.nio.ByteBuffer; | ||
import java.nio.charset.Charset; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.concurrent.Flow; | ||
import org.enso.cli.task.TaskProgressImplementation; | ||
import scala.Option; | ||
import scala.Some; | ||
|
||
/** A {@link HttpResponse} body handler for {@link Path} that keeps track of the progress. */ | ||
public class StringProgressBodyHandler implements HttpResponse.BodyHandler<String> { | ||
private final ByteArrayOutputStream destination = new ByteArrayOutputStream(); | ||
private final TaskProgressImplementation<?> progress; | ||
private final Charset encoding; | ||
private Long total; | ||
|
||
private StringProgressBodyHandler( | ||
TaskProgressImplementation<?> progress, Charset encoding, Long total) { | ||
this.progress = progress; | ||
this.encoding = encoding; | ||
this.total = total; | ||
} | ||
|
||
public static StringProgressBodyHandler of( | ||
TaskProgressImplementation<?> progress, Long sizeHint, Charset encoding) { | ||
return new StringProgressBodyHandler(progress, encoding, sizeHint); | ||
} | ||
|
||
@Override | ||
public HttpResponse.BodySubscriber<String> apply(HttpResponse.ResponseInfo responseInfo) { | ||
if (total == null) { | ||
var reportedLenOpt = responseInfo.headers().firstValueAsLong("Content-Length"); | ||
if (reportedLenOpt.isPresent()) { | ||
total = reportedLenOpt.getAsLong(); | ||
} | ||
} | ||
if (total != null) { | ||
progress.reportProgress(0, Some.apply(total)); | ||
} else { | ||
progress.reportProgress(0, Option.empty()); | ||
} | ||
return new ProgressSubscriber(); | ||
} | ||
|
||
private class ProgressSubscriber implements BodySubscriber<String> { | ||
private Flow.Subscription subscription; | ||
private final CompletableFuture<String> result = new CompletableFuture<>(); | ||
|
||
@Override | ||
public void onSubscribe(Flow.Subscription subscription) { | ||
this.subscription = subscription; | ||
this.subscription.request(Long.MAX_VALUE); | ||
} | ||
|
||
@Override | ||
public void onNext(List<ByteBuffer> items) { | ||
for (ByteBuffer item : items) { | ||
var len = item.remaining(); | ||
progress.reportProgress(len, total == null ? Option.empty() : Some.apply(total)); | ||
byte[] bytes = new byte[len]; | ||
item.get(bytes); | ||
destination.write(bytes, 0, bytes.length); | ||
} | ||
subscription.request(Long.MAX_VALUE); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable throwable) { | ||
try { | ||
destination.close(); | ||
} catch (IOException e) { | ||
throwable.addSuppressed(e); | ||
} | ||
result.completeExceptionally(throwable); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
try { | ||
destination.close(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
result.complete(destination.toString(encoding)); | ||
} | ||
|
||
@Override | ||
public CompletionStage<String> getBody() { | ||
return result; | ||
} | ||
} | ||
} |
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 test is a bit clumsy. If
akka.actor.ActorSystem
is not on the class-path, it only means thatruntime/test
does not haveakka
dependency, it does not check that there are no akka classes inruntime.jar
. However, I still think that this test is useful. Moreover, I have manually checked thatruntime.jar
does not contain any akka classes.