java - 具有 Angular URL 重写功能的嵌入式 Jetty

标签 java angular url-rewriting jetty embedded-jetty

我正在使用 Jetty 部署 WebSocket 和 Angular 应用程序。在开发中,一切正常,但在生产中,我遇到了一个问题,当刷新前端或输入 url 时,我从服务器收到 404 消息,指出给定的资源不存在。现在我正在尝试创建重写规则以将请求重定向到我的index.html。初始化我的服务器如下所示:

server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(config.getServer().getPort());
server.addConnector(connector);

RewriteHandler rewriteHandler = new RewriteHandler();
rewriteHandler.setRewriteRequestURI(true);
rewriteHandler.setRewritePathInfo(false);
rewriteHandler.setOriginalPathAttribute("requestedPath");

/* 
RedirectRegexRule rule1 = new RedirectRegexRule();
rule1.setRegex("/(.+)");
rule1.setLocation("/index.html");

rewriteHandler.addRule(rule1);
*/

URL webRootLocation = this.getClass().getResource("/frontend/index.html");
if (webRootLocation == null)
    throw new IllegalStateException("Unable to determine webroot URL location");

URI webRootUri = URI.create(webRootLocation.toURI().toASCIIString().replaceFirst("/index.html$","/"));
logger.debug("Web Root URI: {}",webRootUri);

ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
contextHandler.setBaseResource(Resource.newResource(webRootUri));
contextHandler.setWelcomeFiles(new String[]{ "index.html" });

rewriteHandler.setHandler(contextHandler);

ServerContainer container = WebSocketServerContainerInitializer.initialize(contextHandler);

List<Class<? extends Encoder>> encoders = new ArrayList<>();
encoders.add(MessageEncoder.class);
List<Class<? extends Decoder>> decoders = new ArrayList<>();
decoders.add(MessageDecoder.class);

ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder
        .create(AppEndpoint.class, "/wss-test")
        .encoders(encoders)
        .decoders(decoders)
        .configurator(new AppEndpointConfig(config, factory))
        .build();
container.addEndpoint(endpointConfig);

//server.setHandler(contextHandler);
HandlerList handlerList = new HandlerList();
handlerList.setHandlers(new Handler[]{rewriteHandler, contextHandler});
server.setHandler(handlerList);

contextHandler.addServlet(DefaultServlet.class, "/");

server.start();
// server.dump(System.err);
server.join();

我的 Angular 前端编译在应用程序的资源文件夹中,该文件夹由服务器提供服务 - 例如:localhost:8080/。如果我的应用程序路由到 localhost:8080/some/path 一切都很好,但刷新页面时我得到一个 404,表示 some/path 未知。使用重写规则(注释掉),我得到 ERR_TOO_MANY_REDIRECTIONS 也找不到延迟加载的资源/模块。关于如何对 Angular 应用程序使用 Jetty 重写的任何建议,因为我不知道如何继续这里,或者 Jetty 是否不支持 angular deployment 建议的 apache 重写之类的东西

RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html

最佳答案

在 Servlet 世界中实现此目的的唯一方法是让错误页面处理 404 状态代码并自行发出重定向。

这只能在特定的 Web 应用程序中完成,而不能从通用重写处理例程中完成。

为什么?

嗯,“如果请求的资源不存在,则使用index.html”是关键。

会发生什么。

  1. 已输入 Web 应用程序,但没有匹配的 url 模式,因此使用默认 url 模式("/")。
  2. 默认 url 模式映射到所谓的“默认 Servlet”
  3. 默认 Servlet 负责在 Web 应用的基础资源中查找匹配的资源,并将该内容作为静态资源请求返回。
  4. 如果静态资源不存在,并且请求是针对目录(例如:/js/),则使用欢迎文件列表来查找欢迎文件。(此列表是在 WEB-INF/web.xml 中配置)
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>
  • 如果静态资源仍然不存在,则将其作为 404 响应进行处理。
  • 同时,servlet 规范的错误页面处理例程开始运行。 这将导致在 WEB-INF/web.xml 中查找为 404 声明的路径。

    <servlet>
      <servletname>404Handler</servlet-name>
      <servlet-class>com.acme.My404Servlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>404Handler</servlet-name>
      <url-pattern>/404status</url-pattern>
    </servlet-mapping>
    <!-- ... then later ... -->
    <error-page>
        <error-code>404</error-code>
        <location>/404status</location>
    </error-page>
    

    该路径可以是 servlet、静态资源、jsp 等。几乎可以是您可以通过路径引用的任何内容。

    如果它是 servlet(或 jsp),您可以通过请求属性询问原始请求,以了解处理此错误的原因。

    参见:https://stackoverflow.com/a/32910916/775715

    嵌入式 jetty 中的一个例子是......

    package jetty.errors;
    
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import javax.servlet.DispatcherType;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.server.handler.DefaultHandler;
    import org.eclipse.jetty.server.handler.HandlerList;
    import org.eclipse.jetty.servlet.DefaultServlet;
    import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    import org.eclipse.jetty.util.resource.PathResource;
    
    public class EmbeddedWelcomeErrorDemo
    {
        public static void main(String[] args) throws Exception
        {
            Server server = new Server(8080);
    
            String baseDir = System.getProperty("user.home");
            if (args.length > 0)
                baseDir = args[0];
    
            Path basePath = Paths.get(baseDir);
    
            if (!Files.exists(basePath) || !Files.isDirectory(basePath))
            {
                throw new IOException("Not a valid directory: " + basePath);
            }
    
            ServletContextHandler context = new ServletContextHandler();
            context.setContextPath("/");
            context.setBaseResource(new PathResource(basePath));
            context.setWelcomeFiles(new String[]{
                "index.html"
            });
    
            // Add error page mapping for context
            context.addServlet(ErrorHandling.class, "/errorpage");
            ErrorPageErrorHandler errorMapper = new ErrorPageErrorHandler();
            errorMapper.addErrorPage(404, "/errorpage");
            context.setErrorHandler(errorMapper);
    
            // to handle static resources against base resource (always last)
            // always named "default" (per spec)
            ServletHolder defaultHolder = new ServletHolder("default", DefaultServlet.class);
            // assigned to default url-pattern of "/" (per spec)
            context.addServlet(defaultHolder, "/");
    
            HandlerList handlers = new HandlerList();
            handlers.addHandler(context);
            handlers.addHandler(new DefaultHandler()); // for non-context errors
    
            server.setHandler(handlers);
            server.start();
            server.join();
        }
    
        public static class ErrorHandling extends HttpServlet
        {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
            {
                if (req.getDispatcherType() != DispatcherType.ERROR)
                {
                    // we didn't get here from a error dispatch.
                    // somebody attempted to use this servlet directly.
                    resp.setStatus(404);
                    return;
                }
    
                String requestedResource = (String)req.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
                log("[ErrorHandling] Requested resource was " + requestedResource);
                int statusCode = (int)req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
                switch (statusCode)
                {
                    case 404:
                        // let handle it by a redirect
                        resp.sendRedirect("/");
                        break;
                    default:
                        // pass the other errors through
                        resp.setStatus(statusCode);
                        break;
                }
            }
        }
    }
    

    发生情况的一些示例。

    $ mkdir $HOME/tmp-base
    $ mdkir css
    $ echo "this is the index.html" > index.html
    $ echo "this is my other html" > myother.html
    $ echo "this is my fancy css" > css/main.css
    

    然后使用命令行运行该目录的服务器示例

    $ java ... jetty.errors.EmbeddedWelcomeErrorDemo $HOME/tmp-base
    2019-09-24 14:17:55.540:INFO::main: Logging initialized @190ms to org.eclipse.jetty.util.log.StdErrLog
    2019-09-24 14:17:55.621:INFO:oejs.Server:main: jetty-9.4.20.v20190813; built: 2019-08-13T21:28:18.144Z; git: 84700530e645e812b336747464d6fbbf370c9a20; jvm 1.8.0_202-b08
    2019-09-24 14:17:55.661:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@7921b0a2{/,file:///home/joakim/tmp-base/,AVAILABLE}
    2019-09-24 14:17:55.674:INFO:oejs.AbstractConnector:main: Started ServerConnector@7cef4e59{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
    2019-09-24 14:17:55.674:INFO:oejs.Server:main: Started @325ms
    

    然后提出一些请求...

    $ curl -L -vv http://localhost:8080/
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:26:28 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
    < Content-Type: text/html
    < Accept-Ranges: bytes
    < Content-Length: 23
    < Server: Jetty(9.4.20.v20190813)
    < 
    this is the index.html
    * Connection #0 to host localhost left intact
    

    这是受欢迎的文件处理

    $ curl -L -vv http://localhost:8080/myother.html
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /myother.html HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:21:10 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:13:46 GMT
    < Content-Type: text/html
    < Accept-Ranges: bytes
    < Content-Length: 22
    < Server: Jetty(9.4.20.v20190813)
    < 
    This is my other html
    * Connection #0 to host localhost left intact
    

    这是正常的静态文件服务

    $ curl -L -vv http://localhost:8080/css/main.css
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /css/main.css HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:22:22 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:22:16 GMT
    < Content-Type: text/css
    < Accept-Ranges: bytes
    < Content-Length: 21
    < Server: Jetty(9.4.20.v20190813)
    < 
    this is my fancy css
    * Connection #0 to host localhost left intact
    

    这是正常的静态文件服务

    如果我向不存在的资源或目录发出一些请求......

    $ curl -L -vv http://localhost:8080/css/bogus.css
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /css/bogus.css HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 302 Found
    < Date: Tue, 24 Sep 2019 19:22:46 GMT
    < Location: http://localhost:8080/
    < Content-Length: 0
    < Server: Jetty(9.4.20.v20190813)
    < 
    * Connection #0 to host localhost left intact
    * Issue another request to this URL: 'http://localhost:8080/'
    * Found bundle for host localhost: 0x5647e1581a50 [can pipeline]
    * Re-using existing connection! (#0) with host localhost
    * Connected to localhost (::1) port 8080 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:22:46 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
    < Content-Type: text/html
    < Accept-Ranges: bytes
    < Content-Length: 23
    < Server: Jetty(9.4.20.v20190813)
    < 
    this is the index.html
    * Connection #0 to host localhost left intact
    

    这是由 ErrorHandling servlet 处理的

    $ curl -L -vv http://localhost:8080/this/directory/does/not/exist
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /this/directory/does/not/exist HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 302 Found
    < Date: Tue, 24 Sep 2019 19:23:02 GMT
    < Location: http://localhost:8080/
    < Content-Length: 0
    < Server: Jetty(9.4.20.v20190813)
    < 
    * Connection #0 to host localhost left intact
    * Issue another request to this URL: 'http://localhost:8080/'
    * Found bundle for host localhost: 0x561eefa8b020 [can pipeline]
    * Re-using existing connection! (#0) with host localhost
    * Connected to localhost (::1) port 8080 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:23:02 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
    < Content-Type: text/html
    < Accept-Ranges: bytes
    < Content-Length: 23
    < Server: Jetty(9.4.20.v20190813)
    < 
    this is the index.html
    * Connection #0 to host localhost left intact
    

    这是由 ErrorHandling servlet 处理的

    [joakim@hyperion tmp]$ curl -L -vv http://localhost:8080/non-existant.jpeg
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /non-existant.jpeg HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 302 Found
    < Date: Tue, 24 Sep 2019 19:21:18 GMT
    < Location: http://localhost:8080/
    < Content-Length: 0
    < Server: Jetty(9.4.20.v20190813)
    < 
    * Connection #0 to host localhost left intact
    * Issue another request to this URL: 'http://localhost:8080/'
    * Found bundle for host localhost: 0x563f476b6a50 [can pipeline]
    * Re-using existing connection! (#0) with host localhost
    * Connected to localhost (::1) port 8080 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.58.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Tue, 24 Sep 2019 19:21:18 GMT
    < Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
    < Content-Type: text/html
    < Accept-Ranges: bytes
    < Content-Length: 23
    < Server: Jetty(9.4.20.v20190813)
    < 
    this is the index.html
    * Connection #0 to host localhost left intact
    

    这是由 ErrorHandling servlet 处理的

    关于java - 具有 Angular URL 重写功能的嵌入式 Jetty,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58086393/

    相关文章:

    java - 使用 java 将文件读取为 1KB block

    angular - 使用 d3-brush 时鼠标按下时出错 : Cannot read property 'touches' of null

    .htaccess 检查两个位置的文件,否则抛出 404

    html - 纯 HTML 网站的友好 URL

    linux - htaccess 将 www 重定向到非 www 不工作

    java - GWT 与 UIBinder - 如何以编程方式添加 ScrollPanel?

    java - 仅具有一个可从所有其他类访问的实例的类

    java - JDK 1.6 和 Xerces?

    angular - 如何正确链接导入的拦截器

    Angular 导航到次要路线,同时保留主要路线