Skip to content

Commit

Permalink
support if-none-match header when putting a resource
Browse files Browse the repository at this point in the history
  • Loading branch information
artsiomkorzun committed Feb 1, 2024
1 parent 47b1d3f commit 163aa7c
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 41 deletions.
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ FROM eclipse-temurin:17-jdk-alpine
# TODO remove the fix once a new version is released
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3

ENV JAVA_OPTS="-Dgflog.config=/app/config/gflog.xml"
ENV OTEL_TRACES_EXPORTER="none"
ENV OTEL_METRICS_EXPORTER="none"
ENV OTEL_LOGS_EXPORTER="none"
Expand All @@ -27,7 +26,6 @@ WORKDIR /app
RUN adduser -u 1001 --disabled-password --gecos "" appuser

COPY --from=builder --chown=appuser:appuser /build/ .
COPY --chown=appuser:appuser ./config/* /app/config/
RUN mkdir /app/log && chown -R appuser:appuser /app

USER appuser
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ Static settings are used on startup and cannot be changed while application is r
| storage.createBucket | false | Indicates whether bucket should be created on start-up
| encryption.password | - | Password used for AES encryption
| encryption.salt | - | Salt used for AES encryption
| encryption.salt | - | Salt used for AES encryption
| resources.maxSize | 1048576 | Max allowed size in bytes for a resource
| resources.syncPeriod | 60000 | Period in milliseconds, how frequently check for resources to sync
| resources.syncDelay | 120000 | Delay in milliseconds for a resource to be written back in object storage after last modification
Expand All @@ -76,7 +75,7 @@ maxmemory 4G
maxmemory-policy volatile-lfu
```

Note: Redis will be strictly required in the upcoming releases 0.7+.
Note: Redis will be strictly required in the upcoming releases 0.8+.

### Dynamic settings

Expand Down
17 changes: 0 additions & 17 deletions config/gflog.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,11 @@ private void load(boolean fail) {
}

private Config loadConfig() throws Exception {
JsonNode tree = null;
JsonNode tree = ProxyUtil.MAPPER.createObjectNode();

for (String path : paths) {
try (InputStream stream = openStream(path)) {
if (tree == null) {
tree = ProxyUtil.MAPPER.readTree(stream);
} else {
tree = ProxyUtil.MAPPER.readerForUpdating(tree).readTree(stream);
}
tree = ProxyUtil.MAPPER.readerForUpdating(tree).readTree(stream);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.epam.aidial.core.util.ProxyUtil;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -106,16 +107,29 @@ private Future<?> putResource(ResourceDescription descriptor) {
return context.respond(HttpStatus.REQUEST_ENTITY_TOO_LARGE, message);
}

String ifNoneMatch = context.getRequest().getHeader(HttpHeaders.IF_NONE_MATCH);
boolean overwrite = (ifNoneMatch == null);

if (ifNoneMatch != null && !ifNoneMatch.equals("*")) {
return context.respond(HttpStatus.BAD_REQUEST, "only header if-none-match=* is supported");
}

return context.getRequest().body().compose(bytes -> {
if (bytes.length() > contentLimit) {
String message = "Resource size: %s exceeds max limit: %s".formatted(bytes.length(), contentLimit);
throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE, message);
}

String body = bytes.toString(StandardCharsets.UTF_8);
return vertx.executeBlocking(() -> service.putResource(descriptor, body));
return vertx.executeBlocking(() -> service.putResource(descriptor, body, overwrite));
})
.onSuccess((metadata) -> {
if (metadata == null) {
context.respond(HttpStatus.CONFLICT, "Resource already exists: " + descriptor.getUrl());
} else {
context.respond(HttpStatus.OK, metadata);
}
})
.onSuccess((metadata) -> context.respond(HttpStatus.OK, metadata))
.onFailure(error -> {
if (error instanceof HttpException exception) {
context.respond(exception.getStatus(), exception.getMessage());
Expand Down
23 changes: 14 additions & 9 deletions src/main/java/com/epam/aidial/core/service/ResourceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ public ResourceService(Vertx vertx,
}

/**
* @param maxSize - max allowed size in bytes for a resource.
* @param syncPeriod - period in milliseconds, how frequently check for resources to sync.
* @param syncDelay - delay in milliseconds for a resource to be written back in object storage after last modification.
* @param syncBatch - how many resources to sync in one go.
* @param cacheExpiration - expiration in milliseconds for synced resources in Redis.
* @param maxSize - max allowed size in bytes for a resource.
* @param syncPeriod - period in milliseconds, how frequently check for resources to sync.
* @param syncDelay - delay in milliseconds for a resource to be written back in object storage after last modification.
* @param syncBatch - how many resources to sync in one go.
* @param cacheExpiration - expiration in milliseconds for synced resources in Redis.
* @param compressionMinSize - compress resources with gzip if their size in bytes more or equal to this value.
*/
public ResourceService(Vertx vertx,
Expand Down Expand Up @@ -192,11 +192,12 @@ public String getResource(ResourceDescription descriptor, boolean lock) {
return result.exists ? result.body : null;
}

public ResourceItemMetadata putResource(ResourceDescription descriptor, String body) {
return putResource(descriptor, body, true);
public ResourceItemMetadata putResource(ResourceDescription descriptor, String body, boolean overwrite) {
return putResource(descriptor, body, overwrite, true);
}

public ResourceItemMetadata putResource(ResourceDescription descriptor, String body, boolean lock) {
public ResourceItemMetadata putResource(ResourceDescription descriptor, String body,
boolean overwrite, boolean lock) {
String redisKey = redisKey(descriptor);
String blobKey = blobKey(descriptor);

Expand All @@ -206,6 +207,10 @@ public ResourceItemMetadata putResource(ResourceDescription descriptor, String b
result = blobGet(blobKey, false);
}

if (result.exists && !overwrite) {
return null;
}

long updatedAt = time();
long createdAt = result.exists ? result.createdAt : updatedAt;
redisPut(redisKey, new Result(body, createdAt, updatedAt, false, true));
Expand All @@ -224,7 +229,7 @@ public void computeResource(ResourceDescription descriptor, Function<String, Str
try (var ignore = lockService.lock(redisKey)) {
String body = getResource(descriptor, false);
String updatedBody = fn.apply(body);
putResource(descriptor, updatedBody, false);
putResource(descriptor, updatedBody, true, false);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/epam/aidial/core/util/HttpStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum HttpStatus {
FORBIDDEN(403),
NOT_FOUND(404),
METHOD_NOT_ALLOWED(405),
CONFLICT(409),
REQUEST_ENTITY_TOO_LARGE(413),
UNSUPPORTED_MEDIA_TYPE(415),
UNPROCESSABLE_ENTITY(422),
Expand Down
21 changes: 18 additions & 3 deletions src/test/java/com/epam/aidial/core/ResourceApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ void testWorkflow() {
response = request(HttpMethod.PUT, "/folder/conversation", "12345");
verifyNotExact(response, 200, "\"url\":\"conversations/3CcedGxCx23EwiVbVmscVktScRyf46KypuBQ65miviST/folder/conversation\"");

response = request(HttpMethod.PUT, "/folder/conversation", "12345", "if-none-match", "*");
verifyNotExact(response, 409, "Resource already exists: conversations/3CcedGxCx23EwiVbVmscVktScRyf46KypuBQ65miviST/folder/conversation");

response = request(HttpMethod.GET, "/folder/conversation");
verify(response, 200, "12345");

Expand Down Expand Up @@ -165,6 +168,12 @@ void testLimit() {
verify(response, 413, "Resource size: 1048577 exceeds max limit: 1048576");
}

@Test
void testUnsupportedIfNoneMatchHeader() {
Response response = request(HttpMethod.PUT, "/folder/big", "1", "if-none-match", "unsupported");
verify(response, 400, "only header if-none-match=* is supported");
}

@Test
void testRandom() {
ThreadLocalRandom random = ThreadLocalRandom.current();
Expand Down Expand Up @@ -226,16 +235,16 @@ private Response request(HttpMethod method, String resource) {
return request(method, resource, "");
}

private Response request(HttpMethod method, String resource, String body) {
return send(method, "/v1/conversations/" + bucket + resource, body);
private Response request(HttpMethod method, String resource, String body, String... headers) {
return send(method, "/v1/conversations/" + bucket + resource, body, headers);
}

private Response metadata(String resource) {
return send(HttpMethod.GET, "/v1/metadata/conversations/" + bucket + resource, "");
}

@SneakyThrows
private Response send(HttpMethod method, String path, String body) {
private Response send(HttpMethod method, String path, String body, String... headers) {
String uri = "http://127.0.0.1:" + dial.getServer().actualPort() + path;
HttpUriRequest request;

Expand All @@ -253,6 +262,12 @@ private Response send(HttpMethod method, String path, String body) {

request.addHeader("api-key", "proxyKey1");

for (int i = 0; i < headers.length; i += 2) {
String key = headers[i];
String value = headers[i + 1];
request.addHeader(key, value);
}

try (CloseableHttpResponse response = client.execute(request)) {
int status = response.getStatusLine().getStatusCode();
String answer = EntityUtils.toString(response.getEntity());
Expand Down

0 comments on commit 163aa7c

Please sign in to comment.