/* * Copyright © 2015 Cask Data, 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 co.cask.cdap.gateway.router; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.common.discovery.ResolvingDiscoverable; import co.cask.http.AbstractHttpHandler; import co.cask.http.BodyConsumer; import co.cask.http.ChunkResponder; import co.cask.http.HttpResponder; import co.cask.http.NettyHttpService; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import org.apache.twill.common.Cancellable; import org.apache.twill.discovery.Discoverable; import org.apache.twill.discovery.DiscoveryService; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.junit.rules.ExternalResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * A generic server for testing router. */ public class ServerResource extends ExternalResource { private static final Logger LOG = LoggerFactory.getLogger(ServerResource.class); static final int CHUNK_SIZE = 1024 * 1024; // NOTE: maxUploadBytes % CHUNK_SIZE == 0 private final String hostname; private final DiscoveryService discoveryService; private final String serviceName; private final AtomicInteger numRequests = new AtomicInteger(0); private NettyHttpService httpService; private Cancellable cancelDiscovery; private byte[] expectedJarBytes; ServerResource(String hostname, DiscoveryService discoveryService, String serviceName) { this.hostname = hostname; this.discoveryService = discoveryService; this.serviceName = serviceName; } @Override protected void before() throws Throwable { NettyRouterPipelineTest.GATEWAY_SERVER.clearNumRequests(); NettyHttpService.Builder builder = NettyHttpService.builder(); builder.addHttpHandlers(ImmutableSet.of(new ServerHandler())); builder.setHost(hostname); builder.setPort(0); httpService = builder.build(); httpService.startAndWait(); registerServer(); LOG.info("Started test server on {}", httpService.getBindAddress()); } @Override protected void after() { httpService.stopAndWait(); } public int getNumRequests() { return numRequests.get(); } public void clearNumRequests() { numRequests.set(0); } public void registerServer() { // Register services of test server LOG.info("Registering service {}", serviceName); cancelDiscovery = discoveryService.register(ResolvingDiscoverable.of(new Discoverable() { @Override public String getName() { return serviceName; } @Override public InetSocketAddress getSocketAddress() { return httpService.getBindAddress(); } })); } public void cancelRegistration() { cancelDiscovery.cancel(); } public void setExpectedJarBytes(byte[] expectedJarBytes) { this.expectedJarBytes = expectedJarBytes; } /** * Simple handler for server. */ public class ServerHandler extends AbstractHttpHandler { @GET @Path("/v1/echo/{name}") public void echo(HttpRequest request, final HttpResponder responder, @PathParam("name") String name) throws InterruptedException, IOException { responder.sendString(HttpResponseStatus.OK, name); } @GET @Path("/v1/repeat/{name}") public void repeat(HttpRequest request, final HttpResponder responder, @PathParam("name") String name) throws InterruptedException, IOException { responder.sendString(HttpResponseStatus.OK, name); } @POST @Path("/v1/upload") public void upload(HttpRequest request, final HttpResponder responder) throws InterruptedException, IOException { ChannelBuffer content = request.getContent(); int readableBytes; int bytesRead = 0; ChunkResponder chunkResponder = responder.sendChunkStart(HttpResponseStatus.OK, ImmutableMultimap.<String, String>of()); while ((readableBytes = content.readableBytes()) > 0) { int read = Math.min(readableBytes, CHUNK_SIZE); bytesRead += read; chunkResponder.sendChunk(content.readSlice(read)); //TimeUnit.MILLISECONDS.sleep(RANDOM.nextInt(1)); } chunkResponder.close(); } @POST @Path("/v1/deploy") public BodyConsumer deploy(HttpRequest request, final HttpResponder responder) throws InterruptedException { return new BodyConsumer() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int count = 0; @Override public void chunk(ChannelBuffer request, HttpResponder responder) { count += request.readableBytes(); if (request.readableBytes() > 0) { } outputStream.write(request.array(), 0, request.readableBytes()); } @Override public void finished(HttpResponder responder) { if (Bytes.compareTo(expectedJarBytes, outputStream.toByteArray()) == 0) { responder.sendStatus(HttpResponseStatus.OK); return; } responder.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); } @Override public void handleError(Throwable cause) { throw Throwables.propagate(cause); } }; } } }