/*
* Copyright 2017 JBoss Inc
*
* 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.apiman.gateway.platforms.vertx3.connector;
import io.apiman.gateway.engine.async.IAsyncHandler;
import io.apiman.gateway.engine.beans.Api;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.io.IApimanBuffer;
import io.apiman.gateway.engine.io.ISignalWriteStream;
import io.apiman.gateway.platforms.vertx3.io.VertxApimanBuffer;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import java.net.URI;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* <p>
* Test to ensure drain handler fires as expected; when the write queue has been exceeded, and
* thus advertises itself as full {@link ISignalWriteStream#isFull}. This enables a simple
* back-pressure mechanism which enables the corresponding ingress point to be paused (thus
* backing off) until load has decreased.
* </p>
* <p>
* The server then calls {@link HttpServerRequest#resume()}, allowing the queued data to flow
* through, simulating a scenario in which the backlog has been cleared. Once the queue has
* sufficiently diminished, {@link ISignalWriteStream#drainHandler(IAsyncHandler)} will be
* called to indicate that the client can begin sending again.
* </p>
* <p>
* Order of operations:
* <ul>
* <li>HTTP server initiated which immediately {@link HttpServerRequest#pause()}es any received
* requests.</li>
* <li>Invoke {@link HttpConnector#write(IApimanBuffer)} on connector until it indicates the
* queue {@link HttpConnector#isFull()}.</li>
* <li>{@link HttpServerRequest#resume} HTTP server to consume (and thus reduce) incoming
* queue.</li>
* <li>{@link ISignalWriteStream#drainHandler(IAsyncHandler)} is invoked once queue is
* sufficiently small (as determined by impl which at time of writing is maxSize/2).</li>
* </ul>
*
* <p>
* The drain handler is sometimes called multiple times, I think it's okay though.
* </p>
*
* @author Marc Savy {@literal <msavy@redhat.com>}
*/
@SuppressWarnings("nls")
@RunWith(VertxUnitRunner.class)
public class HttpConnectorDrainTest {
final Api api = new Api();
{
api.setApiId("");
api.setEndpoint("http://localhost:7297");
api.setOrganizationId("");
api.setParsePayload(false);
api.setPublicAPI(true);
api.setVersion("");
}
final ApiRequest request = new ApiRequest();
{
request.setApi(api);
request.setApiId("");
request.setApiKey("");
request.setApiOrgId("");
request.setApiVersion("");
request.setDestination("/");
request.setType("POST");
}
HttpServer server;
boolean stop = false;
HttpServerRequest pausedRequest = null;
@Before
public void setup() {
}
@Test
public void shouldTriggerDrainHandler(TestContext context) throws Exception {
Async asyncDrain = context.async(2);
Async asyncServer = context.async();
server = Vertx.vertx().createHttpServer()
.connectionHandler(connection -> {
System.out.println("Connection");
})
.requestHandler(requestToPause -> {
System.out.println("Test server: pausing inbound request!");
requestToPause.pause();
pausedRequest = requestToPause;
asyncServer.complete();
requestToPause.handler(data -> {});
}).listen(7297);
Vertx vertx = Vertx.vertx();
HttpConnector httpConnector = new HttpConnector(vertx,
vertx.createHttpClient(),
request,
api,
new HttpConnectorOptions()
.setHasDataPolicy(true)
.setUri(URI.create(api.getEndpoint())),
result -> {});
// Should be fired when write queue reduces to acceptable size.
httpConnector.drainHandler(drain -> {
System.err.println("Drain handler has been called! Yay.");
stop = true;
asyncDrain.complete();
});
// Keep sending until stop is called (or reasonable upper bound.)
for (int i=0; i<100000 && !httpConnector.isFull(); i++) {
httpConnector.write(new VertxApimanBuffer("Anonyme\n"
+ "Aride\n"
+ "Bird Island\n"
+ "Cerf\n"
+ "Chauve Souris\n"
+ "Conception\n"
+ "Cousin\n"
+ "Cousine\n"
+ "Curieuse\n"
+ "Denis Island\n"
+ "Frégate\n"
+ "Félicité\n"
+ "Grande Soeur\n"
+ "Ile Cocos\n"
+ "La Digue\n"
+ "Long Island\n"
+ "Mahé\n"
+ "Moyenne\n"
+ "North Island\n"
+ "Others\n"
+ "Petite Soeur\n"
+ "Praslin\n"
+ "Round Island\n"
+ "Silhouette\n"
+ "St. Pierre\n"
+ "Ste. Anne"));
}
System.out.println("Connection is full? " + httpConnector.isFull());
Assert.assertTrue(httpConnector.isFull());
System.out.println("Waiting for server...");
asyncServer.await();
System.out.println("Connection is still full? " + httpConnector.isFull());
Assert.assertTrue(httpConnector.isFull());
System.out.println("Resuming #pause()d server request; waiting packets should be consumed by the server.");
pausedRequest.resume();
System.out.println("Waiting for drain to be called...");
asyncDrain.await();
System.out.println("Called end on client. Should no longer be full!");
Assert.assertFalse(httpConnector.isFull());
httpConnector.end();
}
}