使用Groovy和超轻量级HTTP服务器通过HTTP公开功能

我需要一种快速简单的方法来使一些用户查询表,并弄清最简单的解决方案是使用嵌入式的轻量级HTTP服务器,以便用户可以在浏览器中键入URL并获取结果。 问题是,当然,哪台服务器最适合。 我想在这里总结一下我发现的选项-包括Gretty,Jetty,Restlet,Jersey等-以及它们的优缺点以及其中大多数的完整示例。 我故意避免使用各种可能轻松支持此功能的框架,例如Grails,因为它感觉并不轻巧,我只需要一个非常简单的临时应用程序。

我使用Groovy来提高生产率,尤其是在JDBC方面。使用GSQL,我只需要两行就可以以用户友好的格式从数据库中获取数据。

我理想的解决方案将使得可以通过对HTTPS和授权的支持来启动服务器,并以编程方式在单个文件(Groovy脚本)中用几行代码声明URL的处理程序。 (非常类似于下面的Gretty解决方案和安全性内容。)

旁注

注意葡萄

Groovy打包引擎Grape可以通过@Grab批注在运行时下载依赖项。 如果您运行groovy脚本f.ex。 通过/ bin / groovy,它将可以正常工作,因为Groovy与Ivy一起分发,这对于Grape来说是必需的。 (如果使用IntelliJ,则将ivy.jar手动添加到项目的类路径中,然后在@Grab批注上调用意图操作(Mac:Alt + Enter)以将其下载并添加到类路径中。)

关于HTTPS / SSL配置的注意事项

要启用HTTPS,您将需要创建带有密钥对的密钥库,Jetty文档 (步骤1a)对此进行了详细介绍

对于急躁的人:

  • keytool -keystore $HOME/.keystore -alias myGroovyServer -genkey -keyalg RSA
  • 当系统询问“您的名字和姓氏是什么?”时,请提供将运行服务的主机名,例如“ localhost”或“ myserver.example.com”
  • 为密钥库和生成的密钥指定相同的密码(例如“ myKeystorePsw”)
  • 运行服务器时,(以服务器特定的方式)提供生成的文件.keystore的(绝对)路径,并将系统属性javax.net.ssl.keyStorePassword设置为密码

1.简单的HTTP请求和响应解决方案

尝试1:贪吃

Gretty是用于Netty(异步Web服务器)的Groovy包装器,是用Groovy ++编写的。 ( 有关Gretty的介绍性文章 。)

优点 :与Groovy集成良好,易于上手,支持服务静态资源,并且Netty很酷。

缺点 :未记录,该项目似乎处于休眠状态,没有添加用户授权和HTTPS的明确方法。

编码:

@GrabConfig(systemClassLoader=true)
@GrabResolver(name='gretty', root='http://groovypp.artifactoryonline.com/groovypp/libs-releases-local')
@Grapes([
    @Grab('org.mbte.groovypp:gretty:0.4.279'),
    @Grab('mysql:mysql-connector-java:5.1.16')])

import org.mbte.gretty.httpserver.*
import groovy.sql.Sql

class Main {

    final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ]

    def run() {
        startServer()
    }

    def getUser(def code) {
        println "Connecting to the DB to check '$code'..."
        def sql = Sql.newInstance( db.url, db.user, db.psw)
        return sql.firstRow("select * from users where code = $code") ?: "No such code found"
    }

    def startServer() {
        GrettyServer server = []
        server.groovy = [
                localAddress: new InetSocketAddress(6789), // no host => all
                defaultHandler: {
                    response.redirect "/"
                },
                "/:code": {
                    get {
                        def user = getUser(it.code)
                        response.text = "The code '${it.code}' refers to $user\n"
                        // => st. like: "The code 'abc' refers to [id:123, name:me@somewhere.no, code:abc]"
                    }
                }
        ]
        server.start()
        println "Groovy server is ready to serve"
    }
}

new Main().run()

码头

优点 :成熟,功能强大,通常以嵌入式形式使用,支持HTTPS授权(也以编程方式)

陷阱 :您不能使用org.eclipse.jetty:jetty-server,因为Grapes.grab无法下载依赖项org.eclipse.jetty.orbit:javax.servlet,这是因为Ivy被打包与扩展混淆了。 使用org.eclipse.jetty。 聚合 :jetty-server代替( Jetty 聚合包合并了多个较小的JAR)。

示例:安全码头

(基于有关通过自定义处理程序或servlet 嵌入Jetty (包括SSL)进行程序配置和处理请求的文章(确实写得很好)以及有关如何通过嵌入式Jetty配置安全性以编程方式配置身份验证和授权的文章)

import groovy.sql.Sql
import javax.servlet.*
import javax.servlet.http.*
import org.eclipse.jetty.server.*
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector
import org.eclipse.jetty.servlet.*
import org.eclipse.jetty.security.*
import org.eclipse.jetty.util.security.*

@GrabConfig(systemClassLoader = true)
@Grapes([
    @Grab('org.eclipse.jetty.aggregate:jetty-server:8.1.2.v20120308'),
    @Grab('org.eclipse.jetty.aggregate:jetty-servlet:8.1.2.v20120308'),
    @Grab(group='javax.servlet', module='javax.servlet-api', version='3.0.1'),
    @Grab('mysql:mysql-connector-java:5.1.16')])
class Main extends HttpServlet {

    final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ]

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        final String code = request.pathInfo.substring(1); // skip leading '/'
        response.setContentType("text/plain");

        try {
            def user = getUser(code)
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().println("Usage of the code '${code}': $user\n")
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
            response.getWriter().println("Connection to the database failed. This may be due to temporary " +
                    "connection problems or due to misconfiguration. Try later.")
        }
    }

    def getUser(def code) {
        println "Connecting to the DB to check '$code'..."
        def sql = Sql.newInstance( db.url, db.user, db.psw)
        return sql.firstRow("select * from users where code = $code") ?: "No such code found"
    }

    public static startServer() {
        Server server = new Server();
        server.setHandler(createServletHandlerWithAuthentication(
                "/", new Main(), createAuthenticationConstraint()))
        server.setConnectors((Connector[])[createSslConnector()])
        server.start();
        server.join();
    }

    /** Wrap the servlet in the servlet handler and configure it to run at the given URL, setting its security handler. */
    private static createServletHandlerWithAuthentication(String contextPath, Servlet servlet, SecurityHandler securityHandler) {
        final String pathSpec = "/*"
        ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
        servletHandler.setContextPath(contextPath)
        servletHandler.setSecurityHandler(securityHandler)
        servletHandler.addServlet(new ServletHolder(servlet), pathSpec)
        return servletHandler
    }

    /** Create HTTPS connector running at port 6789 and using key pair from the hard-coded keystore. */
    private static Connector createSslConnector() {
        SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector()
        ssl_connector.setPort(6789)

        def cf = ssl_connector.getSslContextFactory()
        cf.setKeyStore(System.getProperty("user.home") + "/.keystore")
        cf.setKeyStorePassword("myKeystorePsw")
        cf.setKeyManagerPassword("myKeystorePsw")

        return ssl_connector
    }

    /** Create a security handler requiring authentication with username/password. */
    private static SecurityHandler createAuthenticationConstraint() {
        Constraint constraint = new Constraint();
        constraint.setName(Constraint.__BASIC_AUTH);
        constraint.setRoles((String[])["user"]);
        constraint.setAuthenticate(true);

        ConstraintMapping cm = new ConstraintMapping();
        cm.setConstraint(constraint);
        cm.setPathSpec("/*"); // auth. required for any URL

        def loginSrv = new HashLoginService()
        loginSrv.putUser("myLogin", new Password("myPassword"), (String[])["user"])
        loginSrv.setName("My App Realm")

        SecurityHandler sh = new ConstraintSecurityHandler()
        sh.setLoginService(loginSrv)
        sh.setConstraintMappings((ConstraintMapping[])[cm]);

        return sh
    }
}

Main.startServer()

其他资源:

温斯顿

Winstone是一个200KB的servlet容器, 可通过Maven (最新版本为2008)获得。它似乎专注于服务WAR。

Sun Java 6 HttpServer
Sun JRE 6+包含程序控制的轻量级HTTP服务器 ,还支持HTTPS。 示例代码

2.基于REST的解决方案

球衣JAX-RS

Jersey是JAX-RS(又名REST)的参考实现,可以在嵌入式测试服务器上运行,例如Grizzly,GlassFish或Jetty。

优点 :JAX-RS的参考实现,即标准。

缺点对Jersey进行故障排除并不像我所希望的那样容易。 文档应该更好(与Jetty相比),这确实是一个弱点(尝试找到有关使用嵌入式Grizzly保护Jersey的任何内容)。

示例:带有嵌入式Grizzly的球衣,没有安全性

(如果对安全性和身份验证感兴趣,请查看示例项目https-clientserver-grizzly 。对我来说,这似乎并不复杂。)

import groovy.sql.Sql

import javax.ws.rs.*
import javax.ws.rs.core.*
import com.sun.jersey.api.core.*
import com.sun.jersey.api.container.grizzly2.GrizzlyServerFactory
import org.glassfish.grizzly.http.server.HttpServer

@GrabConfig(systemClassLoader = true)
@GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local')
@Grapes([
    @Grab('com.sun.jersey:jersey-server:1.12'),
    @Grab('com.sun.jersey:jersey-core:1.12'),
    @Grab(group='com.sun.jersey', module='jersey-grizzly2', version='1.12'),
    @Grab(group='javax.ws.rs', module='jsr311-api', version='1.1.1'),
    @Grab('mysql:mysql-connector-java:5.1.16')])

@Path("/{code}")
class Main {

    final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ]

    @GET @Produces("text/plain")
    public Response getUserByCode(@PathParam('code') String code) {
        try {
            def user = getUser(code)
            return Response.ok().entity("Usage of the code '${code}': $user\n".toString()).build();
        } catch (Exception e) {
            Response.serverError().entity("Connection to the database failed. This may be due to temporary " +
                    "connection problems or due to misconfiguration. Try later. Cause: $e".toString()).build();
        }
    }

    def getUser(def code) {
        println "Connecting to the DB to check '$code'..."
        def sql = Sql.newInstance( db.url, db.user, db.psw)
        return sql.firstRow("select * from users where code = $code") ?: "No such code found"
    }

    public static startServer() {
        ResourceConfig resources = new ClassNamesResourceConfig(Main)
        def uri = UriBuilder.fromUri("http://localhost/").port(6789).build();
        HttpServer httpServer = GrizzlyServerFactory.createHttpServer(uri, resources);
        println("Jersey app started with WADL available at ${uri}application.wadl")
        System.in.read();
        httpServer.stop();
    }
}

Main.startServer()

带有嵌入式TJWS的RESTEasy(微型Java Web服务器和Servlet容器)

TJWS确实是微型的,占地100KB,也可在Android上运行,比竞争对手LWSJetty小约5倍。

从RESTEasy文档中:

@Path("")
public class MyResource {

   @GET public String get() { return "hello world"; }

   public static void main(String[] args) throws Exception  {
      TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer();
      tjws.setPort(8081);
      tjws.getRegistry().addPerRequestResource(MyResource.class);
      tjws.start();
   }
}

TJWS本身支持SSL ,我不确定RESTEasy的JBoss TJWS插件(这是Maven中唯一的tjws版本)。 它也可以嵌入,但是无法通过Maven获得,我也不知道它是否支持将请求映射到代码(而不是WAR和JSP)。

带有嵌入式服务器的Restlet

请参阅文章使用Groovy和Restlet构建RESTful Web应用程序,第1部分:启动和运行 (2008)。 由于Restlet在Maven中可用,我们可以@Grab依赖。

更有趣的是GroovyRestlet模块,模块使您只需几行就可以以编程方式配置授权和请求处理 。 (您也可以在Java中使用更多LoC进行此操作。)

版本2.1的文档: 如何实现授权和HTTPS是Java的约6行中最简单的REST服务器

(请注意,Restlet带有一个简单的HTTP服务器,但也可以使用Jetty或Grizzly。)

优点 :RESt(虽然不是标准),与Groovy的集成很好(尽管可能已经过时)

缺点 :从4/2012开始,Restlet仅在其私有Maven存储库中,尽管它们也将在Maven Central中 ,但JAX-RS支持尚未完全实现 (Restlet 2.1-RC3)。 文档可能会更好(更全面,更相互关联,示例更多)。 要使用HTTPS,您必须选择其他服务器而不是内部服务器。

示例:Restlet + SimpleFramework Server + HTTPS和身份验证(无Groovy集成)

import groovy.sql.Sql
import org.restlet.*
import org.restlet.data.*
import org.restlet.resource.*
import org.restlet.security.*

@GrabConfig(systemClassLoader = true)
@GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local')
@GrabResolver(name = 'restlet', root = 'http://maven.restlet.org')
@Grapes([
   @Grab('org.restlet.jse:org.restlet:2.1-RC3'),
   @Grab('org.restlet.jse:org.restlet.ext.simple:2.1-RC3'),
   @Grab('mysql:mysql-connector-java:5.1.16')])
class Main extends ServerResource {

    final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ]

    @Get public String getUser() {
        def code = getRequestAttributes().get("code")
        def user = getUser(code)
        return "Usage of the code '${code}': $user\n"
    }

    def getUser(def code) {

        println "Connecting to the DB to check '$code'..."
        def sql = Sql.newInstance( db.url, db.user, db.psw)
        return sql.firstRow("select * from users where code = $code") ?: "No such code found"
    }

    public static startServer() {
        Component component = new Component();
        def userResourceFinder = component.getDefaultHost().createFinder(Main.class);
        component.getDefaultHost().attach("/{code}"
                , wrapResourceInAuthenticationCheck(component.getContext(), userResourceFinder));
        configureHttpsServer(component, 6789)
        component.start()
    }

    /**
     * Add a Guard (a filter) that asks the user for username/password and checks it against a map.
     */
    private static Restlet wrapResourceInAuthenticationCheck(Context context, Restlet resource) {
        MapVerifier verifier = new MapVerifier();
        verifier.getLocalSecrets().put("myLogin", "myPassword".toCharArray());

        ChallengeAuthenticator guard = new ChallengeAuthenticator(context.createChildContext(), ChallengeScheme.HTTP_BASIC, "My App");
        guard.setVerifier(verifier);
        guard.setNext(resource);

        return guard;
    }

    /**
     * Create the server, instruct it to use a SslContextFactory, and configure the factory with
     * our keystore and password. I guess that which server to use is determined by Restlet based on which
     * package (*.ext.simple.*, *.ext.jetty.* etc.) is available.
     */
    private static void configureHttpsServer(Component component, int port) {
        def secureServer = component.getServers().add(Protocol.HTTPS, port);

        // See http://www.restlet.org/documentation/2.1/jse/ext/org/restlet/ext/ssl/DefaultSslContextFactory.html
        // for params such as keystore path and password
        System.setProperty("javax.net.ssl.keyStorePassword", "myKeystorePsw") // used for keystorePassword & keyPassword
        def confg = secureServer.getContext().getParameters()
        confg.add("sslContextFactory", "org.restlet.ext.ssl.DefaultSslContextFactory")
        // Beware: keystorePath shall default to ${user.home}/.keystore but doesn't seem to do so => set it explicitly
        confg.add("keystorePath", "${System.getProperty('user.home')}/.keystore")
    }
}

Main.startServer()

结论

如果不需要REST,我可能会选择使用Jetty,否则将使用Jersey + Jetty(因为文档要好得多,所以我会选择Jetty而不是Grizzly)。 如果Groovy集成可以工作,并且您不介意使用非标准REST实现,那么Restlet也可能很有趣。

从代码样本的长度来看,尝试Grails或st。 毕竟相似

参考: The Holy Java博客上的JCG合作伙伴 Jakub Holy 公开了Groovy和超轻量级HTTP服务器在HTTP上的功能


翻译自: https://www.javacodegeeks.com/2012/04/exposing-functionality-over-http-with.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值