GithubHelp home page GithubHelp logo

jhannes / action-controller Goto Github PK

View Code? Open in Web Editor NEW
7.0 7.0 3.0 3.89 MB

Action Controller micro framework for easy REST backends

License: Apache License 2.0

Java 99.48% HTML 0.52%
annotations http java library rest server

action-controller's People

Contributors

andreas2s avatar andreassm avatar dependabot[bot] avatar jhannes avatar norrs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

action-controller's Issues

Create HttpRequestParameterMappingFactory

Using a HttpRequestParameterMappingFactory instead of relying on predefined constructors of HttpRequestParameterMapping will avoid possible errors when creating new annotations.

Thanks to @AndreasSM for the suggestion.

Support for declared exceptions, perhaps with annotations

Since OpenAPI specs can specify payloads with 400-responses, it would be interesting to have some sort of support for converting 400-responses to exceptions, especially if the code could be generated from https://github.com/jhannes/openapi-generator-java-annotationfree

Syntax proposal:

@POST("/foo")
@JsonException
public void doThrow() throws MyApplicationException;

public class MyApplicationException {
   public MyApplicationException(SomeErrorDto error) {}

    public SomeErrorDto getSomeError() { ... }
}

Support should be both on the client and server side and rely on throws declarations and POJO exceptions

When a http error is returned it may still be relevant to invoke client callback arguments, e.g.

@POST("/foo")
@JsonException
public void doThrow(
    @HttpHeader("Location") Consumer<String> setLocation,
    @UnencryptedCookie("Session-Id") AtomicReference<String> cookieReference
) throws MyApplicationException;

Should map requests to methods even though there is a potential for conflict

Some APIs come with a certain amount of ambiguity. For instance, one might have the following scenario:

GET /users/{uid}/roles
GET /users/roles/{role}

where the first endpoint returns the roles of a specific user, while the second searches for users with a spesific role

Current situastion:
One cannot handle this in Action-controller due to error "throw new ActionControllerConfigurationException(action + " is in conflict with " + existingAction);"

Behaviour in spring:
Spring has one way of handling these situations: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-requestmapping-pattern-comparison

  • It does not throw any error up front, yet tries to map each request at runtime ("/users/abc/roles" goes to the first, while "/users/roles/abc" to the second)
    -In case of a url that applies equaly to both ("/users/roles/roles", a 403 error is thrown)

Given the following case:

  1. /a/{b}/c
  2. /a/{b}/{c}
    Spring would not throw any error if you try "/a/b/c", but instead regard the first method as "more specific" due to the fact that is only contains one variable, while method two has two variables

Expected behaviour in action-controller:
I would expect action-controller to at least handle the same cases as spring does with regards to determining a method in order to replace existing systems without changes in the API sepc (that is, try runtime to determine a method rather than to throw a "potential conflict"-error )

Either throw an error when no method can be determined by the "most specific" rule, or to let the one with the longest constant prefix prevail.

Home baked cookies (really: support same-site cookies)

In order to get cookies to work across domains, we have to set the Same-Site attribute for the cookie. This can also be critical if integrating between https-servers and localhost-urls for development purposes. There is no support for same-site cookies in the servlet API.

Cookies are headers on the format:

Set-Cookie: cookie1=abc; Same-Site=none; Secure; HttpOnly
Set-Cookie: cookie2=pqr; Same-Site=strict; Secure; HttpOnly
Set-Cookie: toBeDeleted=; Path=/app; HttpOnly; Max-Age=0

The following scenarios must be supported:

Support multipart file uploads

Proposed syntax:

@POST()
public void postData(
    @Multipart("pdfFile") Optional<MultipartFile> pdfFile,
    @Multipart("xmlFile") Optional<MultipartFile> xmlFile,
    @Multipart("diploma") List<MultipartFile> allDiplomas
) {}

Where MultipartFile has properties name, contentType and can return an InputStream (like javax.activation.DataSource)

This can be implemented both as a client and a server mapper

Implement `@IfModifiedSince` and `@LastModified` annotations to simplify checking for not-modified resources

Motivating example:

      @GET("/file/{filename}")
      @ContentBody
       public BufferedInputStream getStream(
                @PathParam("filename") String filename,
                @Modified Consumer<OffsetDateTime> setModified,
                @IfModifiedSince Optional<OffsetDateTime> ifModifiedSince
        ) {
            File file = resolveFile(filename);
            if (ifModifiedSince.map(time -> time.isBefore(file.lastModifiedTime()).orElse(false)) {
                throw new HttpNotModifiedException();
            }
            setModified.apply(file.lastModifiedTime());
            return new FileInputStream(file);
        }
```

ConfigObserver ready for database configuration

ConfigObserver should be adapted to the use case where the configuration is stored in a database like PostgreSQL. The PostgreSQL-specific support should probably not be built-in, but it should be adapted to the case where LISTEN-NOTIFY is used to get instant updates to all instances of a running application

This requires a refactoring of ConfigObserver and ConfigMap classes so that:

  • When a listener is requesting a configuration value, database value is preferred to file value which is preferred to environment
  • When something is changed in the database, we don't erroneously interpret all file-based values as having been removed

Q: Design issue with ApiServlet and expected mapping of /*?

Is it weird to have this expectation in ApiServlet?

org.actioncontroller.ActionControllerConfigurationException: class org.actioncontroller.servlet.ApiServlet should have mapping ending with /*, was [/, /configinfo/, *.jsp, *.jspf, *.jspx, *.xsp, *.JSP, *.JSPF, *.JSPX, *.XSP]

It should be fine to just handle / and /foo and not having the WebAppContext having to handle /*

Example:

internal class AdminWebApp(
    contextPrefix: String, config: ConfigurationBean, dbContext: DbContext,
    dataSourceSupplier: Supplier<DataSource>
) : CommonWebAppContext(contextPrefix) {
    init {
        this.securityHandler =
            basicAuth(
                config.adminUsername,
                config.adminPassword,
                "internal admin"
            )


        val dbContextFilter = addFilter(
            FilterHolder(ApiFilter(dbContext, dataSourceSupplier)),
            "/configinfo",
            EnumSet.of(DispatcherType.REQUEST)
        )
        addServlet(ServletHolder(ApiServlet(ConfigInfoServlet(config))), "/configinfo")

    }
}

Because then it complains about :

javax.servlet.ServletException: org.actioncontroller.servlet.ApiServlet-6cdba6dc==org.actioncontroller.servlet.ApiServlet@9623b286{jsp=null,order=-1,inst=true,async=true,src=EMBEDDED:null}
	at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:643)
	at org.eclipse.jetty.servlet.ServletHolder.initialize(ServletHolder.java:407)
	at org.eclipse.jetty.servlet.ServletHandler.lambda$initialize$2(ServletHandler.java:690)
	at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:714)
	at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:392)
	at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1304)
	at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:879)
	at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:306)
	at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:532)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:171)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:121)
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:89)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:171)
	at org.eclipse.jetty.server.Server.start(Server.java:469)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:89)
	at org.eclipse.jetty.server.Server.doStart(Server.java:414)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
	at Server.start(Server.kt:93)
	at Server$Companion.main(Server.kt:194)
	at Server.main(Server.kt)
Caused by: org.actioncontroller.ActionControllerConfigurationException: class org.actioncontroller.servlet.ApiServlet should have mapping ending with /*, was [/, /configinfo/, *.jsp, *.jspf, *.jspx, *.xsp, *.JSP, *.JSPF, *.JSPX, *.XSP]
	at org.actioncontroller.servlet.ApiServlet.init(ApiServlet.java:137)
	at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:625)
	... 28 more

If app doesn't handle it, is it not the Server's responsibility (in this case jetty) to simply return a 404 from it's error handler?

ConfigObserver support for scanning file file

Example with file glob:

httpClient.serverCertificates=~/.cert/*.crt
public class ClientSocketConfigurationListener extends PrefixConfigListener<SSLSocketFactory> {

    private ClientSocketConfigurationListener(String prefix, ConfigValueListener<SSLSocketFactory> listener) {
        super(prefix, listener);
    }

    @Override
    protected HttpsConfiguration transform(ConfigMap config) throws Exception {
        List<File> files = config.getFiles("serverCertificates");
        if (files.isEmpty) {
            return null;
        }

        KeyStore keyStore = KeyStore.getInstance("pkcs12");
        keyStore.load(null, null);

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
        for (File file : files) {
            try (FileInputStream input = new FileInputStream(file)) {
                keyStore.setCertificateEntry(file.getName(), certificateFactory.generateCertificate(input));
            }
        }

        TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        factory.init(keyStore);
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, factory.getTrustManagers(), null);
        return sslContext.getSocketFactory();
    }
}

   configObserver.onValue("httpClient", ClientSocketConfigurationListener ::new, HttpsURLConnection::setDefaultSSLSocketFactory);

Adding, removing or changing a .crt file under ~/.cert should trigger update

Complex example with both files and other variables

Motivating example (complex): Listening to changes to p12-file to start https connection

https.address=myserver.example.com:8443
https.keyStore=~/.cert/https-key.p12
https.keyStorePassword=***
https.keyPassword=***

This is doubly complex, because the observer should be triggered both when the configuration changes and when the p12-file changes:

    public class HttpsConfiguration {

        final InetSocketAddress address;
        final File keyStoreFile;
        final String keyStorePassword;
        final String keyPassword;

        public HttpsConfiguration(InetSocketAddress address, File keyStoreFile, String keyStorePassword, String keyPassword) {
            this.address= address;
            this.keyStoreFile = keyStoreFile;
            this.keyStorePassword= keyStorePassword;
            this.keyPassword= keyPassword;
        }
    }

    private static class HttpsConfigurationListener extends PrefixConfigListener<HttpsConfiguration> {

        private HttpsConfigurationListener (String prefix, ConfigValueListener<HttpsConfiguration> listener) {
            super(prefix, listener);
        }

        @Override
        protected HttpsConfiguration transform(ConfigMap config) {
            return config.optionalFile("keyStoreFile").map(file -> new HttpsConfiguration(
                    config.optional("address").map(ConfigListener::asInetSocketAddress).orElse(new InetSocketAddress(443)),
                    keyStoreFile,
                    config.getOrDefault("keyStorePassword", null),
                    config.getOrDefault("keyPassword", null),
            )).orElse(null);
        }
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.