Skip to content
4 changes: 3 additions & 1 deletion exonum-light-client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## Unreleased

### Added
* `ExonumClient#findNonEmptyBlocks` to find a certain number of the most recent non-empty
- `ExonumClient#findNonEmptyBlocks` to find a certain number of the most recent non-empty
blocks (from the last block and up to the genesis block). (#953)
- Prefix URL can be set for routing all Light Client requests. (#997)

### Changed
- `ExonumClient#getBlocks` accepts a closed range of block heights _[from; to]_
Expand All @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `ExonumClient#getLastBlocks` returns only the blocks from the closed range
_[max(0, blockchainHeight - count + 1), blockchainHeight]_. The size of
the range _(min(count, blockchainHeight + 1))_ is no longer limited. (#953)
- Now port is optional in the Exonum host URL. (#997)

## 0.2.0 - 2019-05-27

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class Builder {

private URL exonumHost;
private OkHttpClient httpClient = DEFAULT_CLIENT;
private String prefix = "";

/**
* Sets Exonum host url.
Expand Down Expand Up @@ -235,13 +236,23 @@ public Builder setHttpClient(OkHttpClient client) {
return this;
}

/**
* Sets an optional URL prefix to be applied to all requests made by the client.
* Can be helpful in case of using middleware routing proxy on the blockchain node side.
* There is no prefix by default.
*/
public Builder setPrefix(String prefix) {
this.prefix = checkNotNull(prefix);
return this;
}

/**
* Creates Exonum client instance.
* @throws IllegalStateException if required fields weren't set
*/
public ExonumClient build() {
checkRequiredFieldsSet();
return new ExonumHttpClient(httpClient, exonumHost);
return new ExonumHttpClient(httpClient, exonumHost, prefix);
}

private void checkRequiredFieldsSet() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@

import static com.exonum.client.ExonumApi.MAX_BLOCKS_PER_REQUEST;
import static com.exonum.client.ExonumIterables.indexOf;
import static com.exonum.client.ExonumUrls.BLOCK;
import static com.exonum.client.ExonumUrls.BLOCKS;
import static com.exonum.client.ExonumUrls.HEALTH_CHECK;
import static com.exonum.client.ExonumUrls.MEMORY_POOL;
import static com.exonum.client.ExonumUrls.TRANSACTIONS;
import static com.exonum.client.ExonumUrls.USER_AGENT;
import static com.exonum.client.HttpUrlHelper.getFullUrl;
import static com.exonum.client.request.BlockFilteringOption.INCLUDE_EMPTY;
import static com.exonum.client.request.BlockFilteringOption.SKIP_EMPTY;
import static com.exonum.client.request.BlockTimeOption.INCLUDE_COMMIT_TIME;
Expand All @@ -34,6 +34,7 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.util.Collections.emptyMap;

import com.exonum.binding.common.hash.HashCode;
import com.exonum.binding.common.message.TransactionMessage;
Expand All @@ -46,6 +47,7 @@
import com.exonum.client.response.HealthCheckInfo;
import com.exonum.client.response.TransactionResponse;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.URL;
Expand All @@ -72,49 +74,48 @@ class ExonumHttpClient implements ExonumClient {

private final OkHttpClient httpClient;
private final URL exonumHost;
private final String prefix;

ExonumHttpClient(OkHttpClient httpClient, URL exonumHost) {
ExonumHttpClient(OkHttpClient httpClient, URL exonumHost, String prefix) {
this.httpClient = httpClient;
this.exonumHost = exonumHost;
this.prefix = prefix;
}

@Override
public HashCode submitTransaction(TransactionMessage transactionMessage) {
Request request = post(toFullUrl(TRANSACTIONS),
Request request = post(url(TRANSACTIONS),
ExplorerApiHelper.createSubmitTxBody(transactionMessage));

return blockingExecuteAndParse(request, ExplorerApiHelper::parseSubmitTxResponse);
}

@Override
public int getUnconfirmedTransactionsCount() {
Request request = get(toFullUrl(MEMORY_POOL));
Request request = get(url(MEMORY_POOL));

return blockingExecuteAndParse(request, SystemApiHelper::parseMemoryPoolJson);
}

@Override
public HealthCheckInfo healthCheck() {
Request request = get(toFullUrl(HEALTH_CHECK));
Request request = get(url(HEALTH_CHECK));

return blockingExecuteAndParse(request, SystemApiHelper::parseHealthCheckJson);
}

@Override
public String getUserAgentInfo() {
Request request = get(toFullUrl(USER_AGENT));
Request request = get(url(USER_AGENT));

return blockingExecutePlainText(request);
}

@Override
public Optional<TransactionResponse> getTransaction(HashCode id) {
HashCode hash = checkNotNull(id);
HttpUrl url = urlBuilder()
.encodedPath(TRANSACTIONS)
.addQueryParameter("hash", hash.toString())
.build();
Request request = get(url);
Map<String, String> query = ImmutableMap.of("hash", hash.toString());
Request request = get(url(TRANSACTIONS, query));

return blockingExecute(request, response -> {
if (response.code() == HTTP_NOT_FOUND) {
Expand All @@ -140,11 +141,8 @@ public long getBlockchainHeight() {
@Override
public BlockResponse getBlockByHeight(long height) {
checkArgument(0 <= height, "Height can't be negative, but was %s", height);
HttpUrl url = urlBuilder()
.encodedPath(BLOCK)
.addQueryParameter("height", String.valueOf(height))
.build();
Request request = get(url);
Map<String, String> query = ImmutableMap.of("height", String.valueOf(height));
Request request = get(url(BLOCKS, query));

return blockingExecuteAndParse(request, ExplorerApiHelper::parseGetBlockResponse);
}
Expand Down Expand Up @@ -282,11 +280,7 @@ private BlocksResponse doGetBlocks(int count, BlockFilteringOption blockFilter,
if (heightMax != null) {
query.put("latest", String.valueOf(heightMax));
}

HttpUrl.Builder httpRequest = urlBuilder().encodedPath(BLOCKS);
query.forEach(httpRequest::addQueryParameter);

Request request = get(httpRequest.build());
Request request = get(url(BLOCKS, query));

return blockingExecuteAndParse(request, ExplorerApiHelper::parseGetBlocksResponse);
}
Expand All @@ -305,18 +299,12 @@ private static Request post(HttpUrl url, String jsonBody) {
.build();
}

private HttpUrl toFullUrl(String relativeUrl) {
return urlBuilder()
.encodedPath(relativeUrl)
.build();
private HttpUrl url(String path, Map<String, String> query) {
return getFullUrl(exonumHost, prefix, path, query);
}

private HttpUrl.Builder urlBuilder() {

return new HttpUrl.Builder()
.scheme(exonumHost.getProtocol())
.host(exonumHost.getHost())
.port(exonumHost.getPort());
private HttpUrl url(String path) {
return url(path, emptyMap());
}

private <T> T blockingExecute(Request request, Function<Response, T> responseHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
* Contains Exonum API URLs.
*/
final class ExonumUrls {
private static final String EXPLORER_PATHS_PREFIX = "/api/explorer/v1";
private static final String SYS_PATHS_PREFIX = "/api/system/v1";
private static final String EXPLORER_PATHS_PREFIX = "api/explorer/v1";
private static final String SYS_PATHS_PREFIX = "api/system/v1";
static final String TRANSACTIONS = EXPLORER_PATHS_PREFIX + "/transactions";
static final String BLOCK = EXPLORER_PATHS_PREFIX + "/block";
static final String BLOCKS = EXPLORER_PATHS_PREFIX + "/blocks";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2019 The Exonum Team
*
* 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 com.exonum.client;

import static com.google.common.base.Preconditions.checkNotNull;

import java.net.URL;
import java.util.Map;
import okhttp3.HttpUrl;
import okhttp3.HttpUrl.Builder;

final class HttpUrlHelper {

static HttpUrl getFullUrl(URL host, String prefix, String relativeUrl,
Map<String, String> encodedQueryParameters) {
checkNotNull(encodedQueryParameters);
Builder urlBuilder = urlHostBuilder(host)
.addPathSegments(prefix)
.addPathSegments(relativeUrl);
encodedQueryParameters.forEach(urlBuilder::addEncodedQueryParameter);

return urlBuilder.build();
}

private static HttpUrl.Builder urlHostBuilder(URL host) {
checkNotNull(host);
HttpUrl.Builder builder = new HttpUrl.Builder()
.scheme(host.getProtocol())
.host(host.getHost());
if (host.getPort() != -1) {
builder.port(host.getPort());
}
return builder;
}

private HttpUrlHelper() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static com.exonum.client.ExonumApi.MAX_BLOCKS_PER_REQUEST;
import static com.exonum.client.ExonumUrls.BLOCK;
import static com.exonum.client.ExonumUrls.BLOCKS;
import static com.exonum.client.TestUtils.assertPath;
import static com.exonum.client.request.BlockFilteringOption.INCLUDE_EMPTY;
import static com.exonum.client.request.BlockFilteringOption.SKIP_EMPTY;
import static com.exonum.client.request.BlockTimeOption.INCLUDE_COMMIT_TIME;
Expand All @@ -50,7 +51,6 @@
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -123,7 +123,7 @@ void getBlockByHeight() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCK));
assertPath(recordedRequest, BLOCK);
assertThat(recordedRequest.getRequestUrl().queryParameter("height"),
is(String.valueOf(height)));
}
Expand Down Expand Up @@ -160,7 +160,7 @@ void getBlocksSinglePageSkippingEmpty() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1);
assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption);
}
Expand Down Expand Up @@ -197,7 +197,7 @@ void getBlocksSinglePageSkippingEmptyFiltersOutOfRangeBlocks() throws Interrupte
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1);
assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption);
}
Expand Down Expand Up @@ -228,7 +228,7 @@ void getBlocksSinglePageNoTime() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1);
assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption);
}
Expand Down Expand Up @@ -400,7 +400,7 @@ void getLastBlocksSkippingEmptySinglePage() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
assertBlockRequestParams(recordedRequest, numBlocks, blockFilter, null, timeOption);
}

Expand Down Expand Up @@ -437,7 +437,7 @@ void getLastBlocksSkippingEmptyMoreThanCommitted(int overflow)
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
int expectedFirstRequestSize = min(blocksCount, MAX_BLOCKS_PER_REQUEST);
assertBlockRequestParams(recordedRequest, expectedFirstRequestSize, blockFilter, null,
timeOption);
Expand Down Expand Up @@ -659,7 +659,7 @@ void getLastBlock() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
assertBlockRequestParams(recordedRequest, 1, INCLUDE_EMPTY, null, INCLUDE_COMMIT_TIME);
}

Expand All @@ -685,7 +685,7 @@ void getLastNotEmptyBlock() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
assertBlockRequestParams(recordedRequest, 1, SKIP_EMPTY, null, INCLUDE_COMMIT_TIME);
}

Expand All @@ -710,7 +710,7 @@ void getLastNotEmptyBlockNoBlock() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
assertBlockRequestParams(recordedRequest, 1, SKIP_EMPTY, null, INCLUDE_COMMIT_TIME);
}

Expand Down Expand Up @@ -738,7 +738,7 @@ void getBlockchainHeight() throws InterruptedException {
// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), startsWith(BLOCKS));
assertPath(recordedRequest, BLOCKS);
assertBlockRequestParams(recordedRequest, 0, INCLUDE_EMPTY, null, NO_COMMIT_TIME);
}

Expand Down
Loading