All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.fs.http.HttpActiveFs Maven / Gradle / Ivy

Go to download

Provides tools for building efficient, scalable local, remote or clustered file servers. It utilizes ActiveJ CSP for fast and reliable file transfer.

There is a newer version: 6.0-beta2
Show newest version
/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.activej.fs.http;


import io.activej.bytebuf.ByteBuf;
import io.activej.common.exception.MalformedDataException;
import io.activej.csp.ChannelConsumer;
import io.activej.csp.ChannelSupplier;
import io.activej.csp.dsl.ChannelConsumerTransformer;
import io.activej.csp.queue.ChannelZeroBuffer;
import io.activej.fs.ActiveFs;
import io.activej.fs.FileMetadata;
import io.activej.fs.exception.FsExceptionCodec;
import io.activej.http.*;
import io.activej.promise.Promise;
import io.activej.promise.SettablePromise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;
import java.util.Set;

import static io.activej.codec.json.JsonUtils.fromJson;
import static io.activej.codec.json.JsonUtils.toJsonBuf;
import static io.activej.common.Checks.checkArgument;
import static io.activej.common.collection.CollectionUtils.isBijection;
import static io.activej.csp.dsl.ChannelConsumerTransformer.identity;
import static io.activej.fs.http.FsCommand.*;
import static io.activej.fs.util.Codecs.*;
import static io.activej.fs.util.RemoteFsUtils.decodeBody;
import static io.activej.fs.util.RemoteFsUtils.ofFixedSize;
import static io.activej.http.HttpHeaders.CONTENT_LENGTH;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * A client to the remote server with {@link ActiveFsServlet}.
 * This client can be used to connect to publicly available servers.
 * 

* Inherits all of the limitations of {@link ActiveFs} implementation located on server. */ public final class HttpActiveFs implements ActiveFs { private final IAsyncHttpClient client; private final String url; private HttpActiveFs(String url, IAsyncHttpClient client) { this.url = url; this.client = client; } public static HttpActiveFs create(String url, IAsyncHttpClient client) { return new HttpActiveFs(url.endsWith("/") ? url : url + '/', client); } @Override public Promise> upload(@NotNull String name) { return doUpload(name, null); } @Override public Promise> upload(@NotNull String name, long size) { return doUpload(name, size); } @Override public Promise> append(@NotNull String name, long offset) { checkArgument(offset >= 0, "Offset cannot be less than 0"); UrlBuilder urlBuilder = UrlBuilder.relative() .appendPathPart(APPEND) .appendPath(name); if (offset != 0) { urlBuilder.appendQuery("offset", offset); } return uploadData(HttpRequest.post(url + urlBuilder.build()), identity()); } @Override public Promise> download(@NotNull String name, long offset, long limit) { checkArgument(offset >= 0 && limit >= 0); UrlBuilder urlBuilder = UrlBuilder.relative() .appendPathPart(DOWNLOAD) .appendPath(name); if (offset != 0) { urlBuilder.appendQuery("offset", offset); } if (limit != Long.MAX_VALUE) { urlBuilder.appendQuery("limit", limit); } return client.request( HttpRequest.get( url + urlBuilder .build())) .then(HttpActiveFs::checkResponse) .map(HttpMessage::getBodyStream); } @Override public Promise> list(@NotNull String glob) { return client.request( HttpRequest.get( url + UrlBuilder.relative() .appendPathPart(LIST) .appendQuery("glob", glob) .build())) .then(HttpActiveFs::checkResponse) .then(HttpMessage::loadBody) .then(decodeBody(FILE_META_MAP_CODEC)); } @Override public Promise<@Nullable FileMetadata> info(@NotNull String name) { return client.request( HttpRequest.get( url + UrlBuilder.relative() .appendPathPart(INFO) .appendPathPart(name) .build())) .then(HttpActiveFs::checkResponse) .then(HttpMessage::loadBody) .then(decodeBody(FILE_META_CODEC_NULLABLE)); } @Override public Promise> infoAll(@NotNull Set names) { return client.request( HttpRequest.get( url + UrlBuilder.relative() .appendPathPart(INFO_ALL) .build()) .withBody(toJsonBuf(STRINGS_SET_CODEC, names))) .then(HttpActiveFs::checkResponse) .then(HttpMessage::loadBody) .then(decodeBody(FILE_META_MAP_CODEC)); } @Override public Promise ping() { return client.request( HttpRequest.get( url + UrlBuilder.relative() .appendPathPart(PING) .build())) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise move(@NotNull String name, @NotNull String target) { return client.request( HttpRequest.post( url + UrlBuilder.relative() .appendPathPart(MOVE) .appendQuery("name", name) .appendQuery("target", target) .build())) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise moveAll(Map sourceToTarget) { checkArgument(isBijection(sourceToTarget), "Targets must be unique"); if (sourceToTarget.isEmpty()) return Promise.complete(); return client.request( HttpRequest.post( url + UrlBuilder.relative() .appendPathPart(MOVE_ALL) .build()) .withBody(toJsonBuf(SOURCE_TO_TARGET_CODEC, sourceToTarget))) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise copy(@NotNull String name, @NotNull String target) { return client.request( HttpRequest.post( url + UrlBuilder.relative() .appendPathPart(COPY) .appendQuery("name", name) .appendQuery("target", target) .build())) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise copyAll(Map sourceToTarget) { checkArgument(isBijection(sourceToTarget), "Targets must be unique"); if (sourceToTarget.isEmpty()) return Promise.complete(); return client.request( HttpRequest.post( url + UrlBuilder.relative() .appendPathPart(COPY_ALL) .build()) .withBody(toJsonBuf(SOURCE_TO_TARGET_CODEC, sourceToTarget))) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise delete(@NotNull String name) { return client.request( HttpRequest.of(HttpMethod.DELETE, url + UrlBuilder.relative() .appendPathPart(DELETE) .appendPath(name) .build())) .then(HttpActiveFs::checkResponse) .toVoid(); } @Override public Promise deleteAll(Set toDelete) { return client.request( HttpRequest.post( url + UrlBuilder.relative() .appendPathPart(DELETE_ALL) .build()) .withBody(toJsonBuf(STRINGS_SET_CODEC, toDelete))) .then(HttpActiveFs::checkResponse) .toVoid(); } private static Promise checkResponse(HttpResponse response) { switch (response.getCode()) { case 200: case 206: return Promise.of(response); case 500: return response.loadBody() .then(body -> { try { return Promise.ofException(fromJson(FsExceptionCodec.CODEC, body.getString(UTF_8))); } catch (MalformedDataException ignored) { return Promise.ofException(HttpError.ofCode(500)); } }); default: return Promise.ofException(HttpError.ofCode(response.getCode())); } } @NotNull private Promise> doUpload(@NotNull String filename, @Nullable Long size) { UrlBuilder urlBuilder = UrlBuilder.relative().appendPathPart(UPLOAD).appendPath(filename); HttpRequest request = HttpRequest.post(url + urlBuilder.build()); if (size != null) { request.addHeader(CONTENT_LENGTH, String.valueOf(size)); } return uploadData(request, size == null ? identity() : ofFixedSize(size)); } private Promise> uploadData(HttpRequest request, ChannelConsumerTransformer> transformer) { SettablePromise> channelPromise = new SettablePromise<>(); SettablePromise responsePromise = new SettablePromise<>(); client.request(request .withBodyStream(ChannelSupplier.ofPromise(responsePromise .map(response -> { ChannelZeroBuffer buffer = new ChannelZeroBuffer<>(); ChannelConsumer consumer = buffer.getConsumer(); channelPromise.trySet(consumer .transformWith(transformer) .withAcknowledgement(ack -> ack.both(response.loadBody() .then(decodeBody(UploadAcknowledgement.CODEC)) .then(HttpActiveFs::failOnException) .whenException(e -> { channelPromise.trySetException(e); buffer.closeEx(e); })))); return buffer.getSupplier(); })))) .then(HttpActiveFs::checkResponse) .whenException(channelPromise::trySetException) .whenComplete(responsePromise::trySet); return channelPromise; } private static Promise failOnException(UploadAcknowledgement ack) { if (ack.isOk()) { return Promise.complete(); } //noinspection ConstantConditions - checked above return Promise.ofException(ack.getError()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy