package com.mastfrog.acteur; import com.mastfrog.acteur.headers.Headers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.net.MediaType; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.mastfrog.acteur.Acteur.BaseState; import com.mastfrog.giulius.tests.GuiceRunner; import com.mastfrog.giulius.tests.TestWith; import com.mastfrog.guicy.scope.ReentrantScope; import com.mastfrog.acteur.AppTest.M; import com.mastfrog.acteur.util.BasicCredentials; import com.mastfrog.acteur.util.CacheControl; import com.mastfrog.acteur.util.CacheControlTypes; import com.mastfrog.acteur.headers.Method; import com.mastfrog.acteur.server.EventImplFactory; import com.mastfrog.acteur.server.PathFactory; import com.mastfrog.acteur.server.ServerModule; import static com.mastfrog.acteur.server.ServerModule.DELAY_EXECUTOR; import com.mastfrog.acteur.util.RequestID; import com.mastfrog.settings.Settings; import com.mastfrog.util.Checks; import com.mastfrog.util.Codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.util.CharsetUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import org.joda.time.DateTime; import org.joda.time.Duration; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; @RunWith(GuiceRunner.class) @TestWith(M.class) public class AppTest { static ReentrantScope scope = new ReentrantScope(); static class M extends AbstractModule { @Override protected void configure() { bind(Charset.class).toInstance(CharsetUtil.UTF_8); bind(Application.class).to(App.class); ReentrantScope scope = new ReentrantScope(); bind(ReentrantScope.class).toInstance(scope); ExecutorService exe = Executors.newSingleThreadExecutor(); bind(ExecutorService.class).annotatedWith(Names.named(ServerModule.BACKGROUND_THREAD_POOL_NAME)).toInstance(exe); bind(RequestID.class).toInstance(new RequestID.Factory().next()); bind(ScheduledExecutorService.class).annotatedWith(Names.named(DELAY_EXECUTOR)).toInstance(Executors.newScheduledThreadPool(2)); bind(Codec.class).toInstance(new Codec() { final ObjectMapper mapper = new ObjectMapper(); @Override public <T> String writeValueAsString(T object) throws IOException { return mapper.writeValueAsString(object); } @Override public <T> void writeValue(T object, OutputStream out) throws IOException { mapper.writeValue(out, object); } @Override public <T> byte[] writeValueAsBytes(T object) throws IOException { return mapper.writeValueAsBytes(object); } @Override public <T> T readValue(InputStream byteBufInputStream, Class<T> type) throws IOException { return mapper.readValue(byteBufInputStream, type); } }); //Generic madness - Event != Event<?> final Provider<Event> e = binder().getProvider(Event.class); bind(new TypeLiteral<Event<?>>(){}).toProvider(new Provider<Event<?>>() { @Override public Event<?> get() { return e.get(); } }); scope.bindTypes(binder(), Event.class, HttpEvent.class, Page.class, BasicCredentials.class, Thing.class); bind(ThreadFactory.class).annotatedWith(Names.named(ServerModule.WORKER_THREADS)).toInstance(new ThreadFactory() { @Override public Thread newThread(Runnable r) { throw new UnsupportedOperationException("Not supported yet."); } }); } } @Test public void testApp(Application app, PathFactory paths, ReentrantScope scope, Settings settings) throws IOException, InterruptedException, Exception { assertTrue(app instanceof App); assertTrue("App has no pages", app.iterator().hasNext()); Page page = app.iterator().next(); assertNotNull(page); } static class App extends Application { App() { add(P.class); } } static class P extends Page { @Inject P(ActeurFactory f) { add(f.matchMethods(Method.GET)); add(f.matchPath("^root/captcha/image.*")); add(f.requireParameters("select", "value", "realname", "name", "password")); add(AuthenticationAction.class); add(ConvertHeadersAction.class); add(ConvertBodyAction.class); add(WriteResponseAction.class); } } static class AuthenticationAction extends Acteur { @Inject AuthenticationAction(HttpEvent event) { BasicCredentials credentials = event.getHeader(Headers.AUTHORIZATION); if (credentials != null) { next(credentials); System.err.println("CREDENTIALS " + credentials.username + " pw=" + credentials.password); } else { System.err.println("NO CREDENTIALS"); reply(HttpResponseStatus.UNAUTHORIZED); } } } static class ConvertHeadersAction extends Acteur { @Inject ConvertHeadersAction(HttpEvent event) { ReqParams p = event.getParametersAs(ReqParams.class); if (p == null) { System.err.println("PARAM name is " + p.name() + " realname " + p.realname()); setState(new RejectedState()); } else { System.err.println("PARAMS " + p.getClass().getName()); next(p); } } } static class ConvertBodyAction extends Acteur { @Inject ConvertBodyAction(HttpEvent event, ContentConverter cvt) throws IOException { Thing thing = cvt.readObject(event.getContent(), event.getHeader(Headers.CONTENT_TYPE), Thing.class); if (thing == null) { setState(new RejectedState()); } else { next(thing); } } } static boolean done; static class WriteResponseAction extends Acteur { @Inject WriteResponseAction(BasicCredentials creds, Thing thing) { Checks.notNull("creds", creds); Checks.notNull("thing", thing); System.err.println("Got to write response action"); done = true; add(Headers.DATE, new DateTime()); add(Headers.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8); add(Headers.ETAG, "1234"); add(Headers.LAST_MODIFIED, new DateTime().minus(Duration.standardHours(1))); add(Headers.EXPIRES, new DateTime().plus(Duration.standardHours(1))); add(Headers.CACHE_CONTROL, new CacheControl().add(CacheControlTypes.no_store, CacheControlTypes.no_cache)); setResponseCode(HttpResponseStatus.FORBIDDEN); setMessage("Hello " + creds.username + "\n" + thing.foo); next(); } } private Event createEvent(PathFactory paths) throws IOException { ByteBuf buf = Unpooled.directBuffer(256); DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/root/captcha/image/test.png?select=1&value=3&realname=Joe%20Blow&name=joey&password=abcdefg", buf); Headers.write(Headers.DATE, new DateTime(), req); Headers.write(Headers.LAST_MODIFIED, new DateTime().minus(Duration.standardHours(1)), req); Headers.write(Headers.CONTENT_TYPE, MediaType.JSON_UTF_8, req); Headers.write(Headers.AUTHORIZATION, new BasicCredentials("joey", "abcdefg"), req); CacheControl cc = new CacheControl().add(CacheControlTypes.Public, CacheControlTypes.must_revalidate).add(CacheControlTypes.max_age, Duration.standardSeconds(25)); Headers.write(Headers.CACHE_CONTROL, cc, req); CacheControl cc1 = Headers.read(Headers.CACHE_CONTROL, req); assertEquals(cc, cc1); System.err.println("Cache-Control: " + req.headers().get(Headers.CACHE_CONTROL.name())); System.err.println("AUTH HEADER IS " + req.headers().get(HttpHeaders.Names.AUTHORIZATION)); assertNotNull(req.headers().get(HttpHeaders.Names.AUTHORIZATION)); BasicCredentials c = Headers.read(Headers.AUTHORIZATION, req); assertNotNull(c); assertEquals("joey", c.username); assertEquals("abcdefg", c.password); ObjectMapper m = new ObjectMapper(); m.writeValue(new ByteBufOutputStream(buf), new Thing()); return EventImplFactory.newEvent(req, paths); } static interface ReqParams { String realname(); int value(); int select(); String name(); String hashcode(); } static class Thing { public String foo = "Foo foo fru"; public long bar = System.currentTimeMillis(); } static class R implements ResponseSender { State received; private int count = 0; private int max = 5; void await() throws InterruptedException { synchronized (this) { while (received == null) { count++; wait(1000); if (count == max) { if (this.received == null) { fail("Never called"); } break; } } } } @Override public void receive(Acteur action, com.mastfrog.acteur.State state, ResponseImpl response) { assertNotNull(state); System.err.println("Received " + state); synchronized (this) { this.received = state; notifyAll(); } System.err.println(response); } @Override public void uncaughtException(Thread thread, Throwable thrwbl) { thrwbl.printStackTrace(); fail(thrwbl.getMessage()); } } }