content: import io.activej.bytebuf.ByteBuf;
  import io.activej.bytebuf.ByteBufStrings;
  import io.activej.csp.consumer.ChannelConsumer;
  import io.activej.csp.file.ChannelFileWriter;
  import io.activej.dns.DnsClient;
  import io.activej.dns.IDnsClient;
  import io.activej.http.*;
  import io.activej.http.MultipartByteBufsDecoder.AsyncMultipartDataHandler;
  import io.activej.inject.Injector;
  import io.activej.inject.annotation.Inject;
  import io.activej.inject.annotation.Provides;
  import io.activej.launcher.Launcher;
  import io.activej.launchers.http.HttpServerLauncher;
  import io.activej.promise.Promise;
  import io.activej.reactor.Reactor;
  import io.activej.reactor.nio.NioReactor;
  
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.Executor;
  import java.util.concurrent.Executors;
  
  import static io.activej.http.HttpMethod.POST;
  
  public final class MultipartDataHandlingExample extends HttpServerLauncher {
  	private static final String BOUNDARY = "-----------------------------4336597275426690519140415448";
  	private static final String MULTIPART_REQUEST = """
  		$boundary\r
  		Content-Disposition: form-data; name="id"\r
  		\r
  		12345\r
  		$boundary\r
  		Content-Disposition: form-data; name="file1"; filename="data.txt"\r
  		Content-Type: text/plain\r
  		\r
  		Contet of data.txt\r
  		$boundary\r
  		Content-Disposition: form-data; name="first name"\r
  		\r
  		Alice\r
  		$boundary\r
  		Content-Disposition: form-data; name="file2"; filename="key.txt"\r
  		Content-Type: text/html\r
  		\r
  		Content of key.txt\r
  		$boundary\r
  		Content-Disposition: form-data; name="last name"\r
  		\r
  		Johnson\r
  		$boundary--\r
  		"""
  		.replace("$boundary", BOUNDARY);
  
  	private Path path;
  	private int fileUploadsCount;
  
  	@Override
  	protected void onInit(Injector injector) throws Exception {
  		path = Files.createTempDirectory("multipart-data-files");
  	}
  
  	@Inject
  	Reactor reactor;
  
  	@Inject
  	Executor executor;
  
  	@Inject
  	IHttpClient client;
  
  	@Provides
  	IDnsClient dnsClient(NioReactor reactor) {
  		return DnsClient.create(reactor, HttpUtils.inetAddress("8.8.8.8"));
  	}
  
  	@Provides
  	IHttpClient client(NioReactor reactor, IDnsClient dnsClient) {
  		return HttpClient.create(reactor, dnsClient);
  	}
  
  	@Provides
  	Executor executor() {
  		return Executors.newSingleThreadExecutor();
  	}
  
  	//[START SERVLET]
  	@Provides
  	AsyncServlet servlet(Reactor reactor) {
  		return RoutingServlet.builder(reactor)
  			.with(POST, "/handleMultipart", request -> {
  				Map<String, String> fields = new HashMap<>();
  
  				return request.handleMultipart(AsyncMultipartDataHandler.fieldsToMap(fields, this::upload))
  					.then($ -> {
  						logger.info("Received fields: {}", fields);
  						logger.info("Uploaded {} files total", fileUploadsCount);
  						return HttpResponse.ok200().toPromise();
  					});
  			})
  			.build();
  	}
  	//[END SERVLET]
  
  	@Override
  	protected void run() throws ExecutionException, InterruptedException {
  		CompletableFuture<Integer> future = reactor.submit(() ->
  			client.request(HttpRequest.post("http://localhost:8080/handleMultipart")
  					.withHeader(HttpHeaders.CONTENT_TYPE, "multipart/form-data; boundary=" + BOUNDARY.substring(2))
  					.withBody(ByteBufStrings.encodeAscii(MULTIPART_REQUEST))
  					.build())
  				.map(HttpResponse::getCode));
  
  		int code = future.get();
  
  		if (code != 200) {
  			throw new RuntimeException("Did not receive OK response: " + code);
  		}
  	}
  
  	//[START UPLOAD]
  	private Promise<ChannelConsumer<ByteBuf>> upload(String filename) {
  		logger.info("Uploading file '{}' to {}", filename, path);
  		return ChannelFileWriter.open(executor, path.resolve(filename))
  			.map(writer -> writer.withAcknowledgement(ack ->
  				ack.whenResult(() -> {
  					logger.info("Upload of file '{}' finished", filename);
  					fileUploadsCount++;
  				})));
  	}
  	//[END UPLOAD]
  
  	public static void main(String[] args) throws Exception {
  		Launcher launcher = new MultipartDataHandlingExample();
  		launcher.launch(args);
  	}
  }
  