use of org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody in project nakadi by zalando.
the class EventStreamControllerTest method writeStream.
private void writeStream() throws Exception {
final StreamingResponseBody responseBody = createStreamingResponseBody(new NakadiClient("clientId", ""));
final OutputStream outputStream = mock(OutputStream.class);
responseBody.writeTo(outputStream);
}
use of org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody in project nakadi by zalando.
the class EventStreamControllerTest method whenTopicNotExistsThenTopicNotFound.
@Test
public void whenTopicNotExistsThenTopicNotFound() throws IOException, NakadiException {
when(eventTypeRepository.findByName(TEST_EVENT_TYPE_NAME)).thenThrow(NoSuchEventTypeException.class);
final StreamingResponseBody responseBody = createStreamingResponseBody();
final Problem expectedProblem = Problem.valueOf(NOT_FOUND, "topic not found");
assertThat(responseToString(responseBody), TestUtils.JSON_TEST_HELPER.matchesObject(expectedProblem));
}
use of org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody in project nakadi by zalando.
the class EventStreamControllerTest method whenStreamTimeoutLowerThanBatchTimeoutThenUnprocessableEntity.
@Test
public void whenStreamTimeoutLowerThanBatchTimeoutThenUnprocessableEntity() throws NakadiException, IOException {
when(eventTypeRepository.findByName(TEST_EVENT_TYPE_NAME)).thenReturn(EVENT_TYPE);
final StreamingResponseBody responseBody = createStreamingResponseBody(0, 0, 20, 10, 0, null);
final Problem expectedProblem = Problem.valueOf(UNPROCESSABLE_ENTITY, "stream_timeout can't be lower than batch_flush_timeout");
assertThat(responseToString(responseBody), TestUtils.JSON_TEST_HELPER.matchesObject(expectedProblem));
}
use of org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody in project nakadi by zalando.
the class EventStreamControllerTest method reportCurrentNumberOfConsumers.
@Test
public void reportCurrentNumberOfConsumers() throws Exception {
when(eventTypeRepository.findByName(TEST_EVENT_TYPE_NAME)).thenReturn(EVENT_TYPE);
final EventStream eventStream = mock(EventStream.class);
// block to simulate the streaming until thread is interrupted
Mockito.doAnswer(invocation -> {
while (!Thread.interrupted()) {
Thread.sleep(100);
}
return null;
}).when(eventStream).streamEvents(any(), any());
when(eventStreamFactoryMock.createEventStream(any(), any(), any(), any())).thenReturn(eventStream);
// "connect" to the server
final StreamingResponseBody responseBody = createStreamingResponseBody();
final LinkedList<Thread> clients = new LinkedList<>();
final Counter counter = metricRegistry.counter(metricNameFor(TEST_EVENT_TYPE_NAME, EventStreamController.CONSUMERS_COUNT_METRIC_NAME));
// create clients...
for (int i = 0; i < 3; i++) {
final Thread client = new Thread(() -> {
try {
responseBody.writeTo(new ByteArrayOutputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
});
client.start();
clients.add(client);
TestUtils.waitFor(() -> assertThat(counter.getCount(), equalTo((long) clients.size())), TimeUnit.SECONDS.toMillis(5));
}
// ...and disconnect them one by one
while (!clients.isEmpty()) {
final Thread client = clients.pop();
client.interrupt();
client.join();
assertThat(counter.getCount(), equalTo((long) clients.size()));
}
}
use of org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody in project nakadi by zalando.
the class EventStreamController method streamEvents.
@RequestMapping(value = "/event-types/{name}/events", method = RequestMethod.GET)
public StreamingResponseBody streamEvents(@PathVariable("name") final String eventTypeName, @Nullable @RequestParam(value = "batch_limit", required = false) final Integer batchLimit, @Nullable @RequestParam(value = "stream_limit", required = false) final Integer streamLimit, @Nullable @RequestParam(value = "batch_flush_timeout", required = false) final Integer batchTimeout, @Nullable @RequestParam(value = "stream_timeout", required = false) final Integer streamTimeout, @Nullable @RequestParam(value = "stream_keep_alive_limit", required = false) final Integer streamKeepAliveLimit, @Nullable @RequestHeader(name = "X-nakadi-cursors", required = false) final String cursorsStr, final HttpServletRequest request, final HttpServletResponse response, final Client client) {
final String flowId = FlowIdUtils.peek();
return outputStream -> {
FlowIdUtils.push(flowId);
if (blacklistService.isConsumptionBlocked(eventTypeName, client.getClientId())) {
writeProblemResponse(response, outputStream, Problem.valueOf(Response.Status.FORBIDDEN, "Application or event type is blocked"));
return;
}
final AtomicBoolean connectionReady = closedConnectionsCrutch.listenForConnectionClose(request);
Counter consumerCounter = null;
EventStream eventStream = null;
List<ConnectionSlot> connectionSlots = ImmutableList.of();
final AtomicBoolean needCheckAuthorization = new AtomicBoolean(false);
LOG.info("[X-NAKADI-CURSORS] \"{}\" {}", eventTypeName, Optional.ofNullable(cursorsStr).orElse("-"));
try (Closeable ignore = eventTypeChangeListener.registerListener(et -> needCheckAuthorization.set(true), Collections.singletonList(eventTypeName))) {
final EventType eventType = eventTypeRepository.findByName(eventTypeName);
authorizeStreamRead(eventTypeName);
// validate parameters
final EventStreamConfig streamConfig = EventStreamConfig.builder().withBatchLimit(batchLimit).withStreamLimit(streamLimit).withBatchTimeout(batchTimeout).withStreamTimeout(streamTimeout).withStreamKeepAliveLimit(streamKeepAliveLimit).withEtName(eventTypeName).withConsumingClient(client).withCursors(getStreamingStart(eventType, cursorsStr)).withMaxMemoryUsageBytes(maxMemoryUsageBytes).build();
// acquire connection slots to limit the number of simultaneous connections from one client
if (featureToggleService.isFeatureEnabled(LIMIT_CONSUMERS_NUMBER)) {
final List<String> partitions = streamConfig.getCursors().stream().map(NakadiCursor::getPartition).collect(Collectors.toList());
connectionSlots = consumerLimitingService.acquireConnectionSlots(client.getClientId(), eventTypeName, partitions);
}
consumerCounter = metricRegistry.counter(metricNameFor(eventTypeName, CONSUMERS_COUNT_METRIC_NAME));
consumerCounter.inc();
final String kafkaQuotaClientId = getKafkaQuotaClientId(eventTypeName, client);
response.setStatus(HttpStatus.OK.value());
response.setHeader("Warning", "299 - nakadi - the Low-level API is deprecated and will " + "be removed from a future release. Please consider migrating to the Subscriptions API.");
response.setContentType("application/x-json-stream");
final EventConsumer eventConsumer = timelineService.createEventConsumer(kafkaQuotaClientId, streamConfig.getCursors());
final String bytesFlushedMetricName = MetricUtils.metricNameForLoLAStream(client.getClientId(), eventTypeName);
final Meter bytesFlushedMeter = this.streamMetrics.meter(bytesFlushedMetricName);
eventStream = eventStreamFactory.createEventStream(outputStream, eventConsumer, streamConfig, bytesFlushedMeter);
// Flush status code to client
outputStream.flush();
eventStream.streamEvents(connectionReady, () -> {
if (needCheckAuthorization.getAndSet(false)) {
authorizeStreamRead(eventTypeName);
}
});
} catch (final UnparseableCursorException e) {
LOG.debug("Incorrect syntax of X-nakadi-cursors header: {}. Respond with BAD_REQUEST.", e.getCursors(), e);
writeProblemResponse(response, outputStream, BAD_REQUEST, e.getMessage());
} catch (final NoSuchEventTypeException e) {
writeProblemResponse(response, outputStream, NOT_FOUND, "topic not found");
} catch (final NoConnectionSlotsException e) {
LOG.debug("Connection creation failed due to exceeding max connection count");
writeProblemResponse(response, outputStream, e.asProblem());
} catch (final NakadiException e) {
LOG.error("Error while trying to stream events.", e);
writeProblemResponse(response, outputStream, e.asProblem());
} catch (final InvalidCursorException e) {
writeProblemResponse(response, outputStream, PRECONDITION_FAILED, e.getMessage());
} catch (final AccessDeniedException e) {
writeProblemResponse(response, outputStream, FORBIDDEN, e.explain());
} catch (final Exception e) {
LOG.error("Error while trying to stream events. Respond with INTERNAL_SERVER_ERROR.", e);
writeProblemResponse(response, outputStream, INTERNAL_SERVER_ERROR, e.getMessage());
} finally {
connectionReady.set(false);
consumerLimitingService.releaseConnectionSlots(connectionSlots);
if (consumerCounter != null) {
consumerCounter.dec();
}
if (eventStream != null) {
eventStream.close();
}
try {
outputStream.flush();
} finally {
outputStream.close();
}
}
};
}
Aggregations