micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.04k stars 1.06k forks source link

Simplify Creation of Own HTTP Server Implementation #367

Closed musketyr closed 6 years ago

musketyr commented 6 years ago

Rationale

I have created my own HTTP Server (semi-)implementation which creates HttpRequest from a native request event and then parses HttpResponse into native response event for using Micronaut's HTTP features in API Gateway Lambda Proxy environment (see Micronaut Function AWS AGP). The main reason for this is to be able to run the same code using this custom implementation using API Gateway but also locally using Netty server. While implementing the server I've found out that only part which is properly separated from Netty is the router. Argument binding, filters, exception handling and more happens in the Netty server project. I was able to cherry-pick the imporant classes but I know this may produce serious maintainability issues in the future.

These are the major blockers in the Micronaut codebase to create HTTP server implemenation easily

1) RequestArgumentSatisfier is kept in Netty project even there is only one Netty specific call (((NettyHttpRequest) request).setBodyRequired(true);). It should be relatively easy to pull up to http server module. 2) Exception, filter and status handling happens in RoutingInBoundHandler even the behaviour of @Filter, @Status and @Error should be guaranteed by basic HTTP server module. Cherry-picking this one was very tedious but i believe that the main functionallity can be extracted from the Netty specific implementation. 3) Would be great to have implementation of ByteBuffer based on Java NIO which can be used as @Secondary if Netty implementation is missing 4) ~~SoftServiceLoader is not flexible enough. If I don't want to end up in classpath configuration hell and to be able to simply run tests agains custom and Netty implementation (see ApiGatewayProxyTestSuite) I need a way how to point HttpResponseFactory#INSTANCE to either CustomHttpResponseFactory or default NettyHttpResponseFactory. It would be optimal if SoftServiceLoader allowed to use @Requires or similar simplified annotation which would accept env and notEnv. I had to create interface HttpResponder to be able to create the HttpResponse in environment-aware way.~~

Expected Behaviour

It should be easy to create your own HTTP server implementation on HttpRequest => HttpResponse basis.

Actual Behaviour

Most of the code is hidden in Netty related projects.

Environment Information

Example Application

https://github.com/agorapulse/micronaut-libraries

musketyr commented 6 years ago

ad 3) as a quick fix if would be great at least have http-netty-buffer module which only contains the buffer implementation from io.micronaut.http.netty.buffer and depends on io.netty:netty-buffer. this would reduce two classes from the maintainability burden and enable easier way how to exclude the netty buffer implementation from the classpath in future.

musketyr commented 6 years ago

ad 4) I was able to solve the SoftServiceLoader problem with some Gradle classpath magic, so this is not that important anymore

cwbeck commented 3 years ago

Is it possible to create your own HttpRequest - or at least an extend version of it? I've been unable todo so and it is quite frustrating. Moving over from Scala and PlayFramework, there seems to be quite a bit missing here?

package com.scale8.extended;

import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpRequestWrapper;
import java.util.Optional;

public class ExtendedHttpRequest<B> extends HttpRequestWrapper<B> {

  public ExtendedHttpRequest(HttpRequest<B> delegate) {
    super(delegate);
  }

  public Optional<String> getBodyAsString() {
    return super.getBody(Argument.of(String.class));
  }
}

then...

package com.scale8;

import com.scale8.extended.ExtendedHttpRequest;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.FilterChain;
import io.micronaut.http.filter.HttpFilter;
import org.reactivestreams.Publisher;

@Filter("/**")
public class AllRouteFilter implements HttpFilter {
    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(HttpRequest<?> request, FilterChain chain) {
        return chain.proceed(new ExtendedHttpRequest<>(request));
    }
}

But there is no way to receive it back?

  @Get(uri = "/test", produces = APPLICATION_JSON)
  public String test(HttpRequest<String> request) throws Exception {
    return "...";
  }

Perhaps there is something I have missed here.