Servlet 返回“HTTP 状态 404 请求的资源 (/servlet) 不可用”

新手上路,请多包涵

我的 WebContent/jsps 文件夹中的 JSP 文件中有一个 HTML 表单。我在 src 文件夹中的默认包中有一个 servlet 类 servlet.java 。在我的 web.xml 它被映射为 /servlet

我在 HTML 表单的 action 属性中尝试了几个 URL:

 <form action="/servlet">

 <form action="/servlet.java">

 <form action="/src/servlet.java">

 <form action="../servlet.java">

但这些都不起作用。它们都在 Tomcat 6/7/8 中不断返回 HTTP 404 错误,如下所示:

HTTP 状态 404 — /servlet

_说明_:请求的资源 (/servlet) 不可用。

或者在 Tomcat 8.59 中如下所示:

HTTP 状态 404 - 未找到

_消息_:/servlet

_描述_:源服务器没有找到目标资源的当前表示或不愿意透露存在的表示

或者在 Tomcat 10 中如下所示:

HTTP 状态 404 - 未找到

_类型_:状态报告

_消息_:请求的资源 (/servlet) 不可用

_描述_:源服务器没有找到目标资源的当前表示或不愿意透露存在的表示

为什么它不起作用?

原文由 pongahead 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 4k
1 个回答

介绍

这可能有很多原因,这些原因在以下部分中进行了细分:

  • 将 servlet 类放在 package
  • url-pattern
  • @WebServlet 仅适用于 Servlet 3.0 或更新版本
  • javax.servlet.* 在 Servlet 5.0 或更新版本中不再工作
  • 确保已编译的 *.class 文件存在于构建的 WAR 中
  • 在没有任何 JSP/HTML 页面的情况下单独测试 servlet
  • 使用域相对 URL 从 HTML 引用 servlet
  • 在 HTML 属性中使用直引号

将 servlet 类放在 package

首先,将 servlet 类放在 Java package 中。您应该 始终 将可公开重用的 Java 类放在一个包中,否则它们对于包中的类是不可见的,例如服务器本身。这样您就可以消除潜在的环境特定问题。 Packageless servlets 仅在特定的 Tomcat+JDK 组合中工作,永远不要依赖它。

在“普通”IDE 项目的情况下,类需要放置在“Java Sources”文件夹内的包结构中, _而不是_“Web Content”文件夹内,后者用于 JSP 等 Web 文件。下面是在 Navigator 视图中看到的默认 Eclipse 动态 Web 项目 的文件夹结构示例(“Java Sources”文件夹在此类项目中默认由 src 文件夹表示):

 EclipseProjectName
 |-- src
 |    `-- com
 |         `-- example
 |              `-- YourServlet.java
 |-- WebContent
 |    |-- WEB-INF
 |    |    `-- web.xml
 |    `-- jsps
 |         `-- page.jsp
 :

对于 Maven 项目,该类需要放在其包结构中 main/java不是 main/resources这是针对非类文件的,绝对 也不main/webapp ,这是用于网络文件。下面是在 Eclipse 的 导航器 视图中看到的默认 Maven webapp 项目的文件夹结构示例:

 MavenProjectName
 |-- src
 |    `-- main
 |         |-- java
 |         |    `-- com
 |         |         `-- example
 |         |              `-- YourServlet.java
 |         |-- resources
 |         `-- webapp
 |              |-- WEB-INF
 |              |    `-- web.xml
 |              `-- jsps
 |                   `-- page.jsp
 :

请注意, /jsps 子文件夹并非绝对必要。您甚至可以不使用它并将 JSP 文件直接放在 webcontent/webapp 根目录中,但我只是从您的问题中接管了它。

url-pattern

servlet URL 被指定为 servlet 映射的“URL 模式”。根据定义,它绝对不是 servlet 类的类名/文件名。 URL 模式将被指定为 @WebServlet 注释的值。

 package com.example; // Use a package!

import jakarta.servlet.annotation.WebServlet; // or javax.*
import jakarta.servlet.http.HttpServlet; // or javax.*

@WebServlet("/servlet") // This is the URL of the servlet.
public class YourServlet extends HttpServlet { // Must be public and extend HttpServlet.
    // ...
}

如果你想支持像 /servlet/foo/bar 这样的路径参数,那么使用 /servlet/* 的 URL 模式。另见 Servlet 和路径参数,如 /xyz/{value}/test,如何在 web.xml 中映射?

请注意,使用 /*/ 的 Servlet URL 模式试图拥有“前端控制器”被认为是一种不好的做法。因此,不要滥用这些 URL 模式来尝试捕获所有 URL。有关深入的解释,另请参阅 Difference between / and /* in servlet mapping url pattern

@WebServlet 仅适用于 Servlet 3.0 或更新版本

为了使用 @WebServlet ,您只需要确保您的 web.xml 文件(如果有的话)(自 Servlet 3.0 以来它是可选的)被声明为符合 Servlet 3.0+ 版本 ,因此 符合例如 2.5 版本或更低版本。它绝对也不应该有任何 <!DOCTYPE> 行。下面是一个完整的 Servlet 6.0 兼容版本(匹配 Tomcat 10.1+、WildFly 27+(预览版)、GlassFish/Payara 7+ 等):

 <?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
    version="6.0"
>
    <!-- Config here. -->
</web-app>

下面是一个 Servlet 5.0 兼容的(匹配 Tomcat 10.0.x、WildFly 22+(预览版)、GlassFish/Payara 6+ 等)。

 <?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
    version="5.0"
>
    <!-- Config here. -->
</web-app>

下面是一个 Servlet 4.0 兼容的(匹配 Tomcat 9+、WildFly 11+、GlassFish/Payara 5+ 等)。

 <?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0"
>
    <!-- Config here. -->
</web-app>

或者,如果您还没有使用 Servlet 3.0+(例如 Tomcat 6 或更早版本),则删除 @WebServlet 注释。

 package com.example;

import javax.servlet.http.HttpServlet;

public class YourServlet extends HttpServlet {
    // ...
}

并在 web.xml 中注册 servlet,如下所示:

 <servlet>
    <servlet-name>yourServlet</servlet-name>
    <servlet-class>com.example.YourServlet</servlet-class> <!-- Including the package thus -->
</servlet>
<servlet-mapping>
    <servlet-name>yourServlet</servlet-name>
    <url-pattern>/servlet</url-pattern>  <!-- This is the URL of the servlet. -->
</servlet-mapping>

因此请注意,您不应同时使用这两种方式。使用基于注释的配置或基于 XML 的配置。当你同时拥有两者时,基于 XML 的配置将覆盖基于注释的配置。

javax.servlet.* 在 Servlet 5.0 或更新版本中不再工作

从 Jakarta EE 9 / Servlet 5.0(Tomcat 10、TomEE 9、WildFly 22 Preview、GlassFish 6、Payara 6、Liberty 22 等)开始, javax.* 包已重命名为 jakarta.* 包裹。

换句话说,请绝对确保您不会随意将不同服务器的 JAR 文件放入您的 WAR 项目中,例如 tomcat-servlet-api-9.xxjar 仅仅是为了获得 javax.* 包编译。这只会带来麻烦。完全删除它并编辑您的 servlet 类的导入

import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

import jakarta.servlet.*;
import jakarta.servlet.annotation.*;
import jakarta.servlet.http.*;

如果您使用的是 Maven,您可以在这个答案中找到 Tomcat 10+、Tomcat 9-、JEE 9+ 和 JEE 8- 的正确 pom.xml 声明示例: How to properly configure Jakarta EE libraries in Tomcat 的 Maven pom.xml? 另一种方法是将服务器降级到旧版本,例如从 Tomcat 10 降级到 Tomcat 9 或更早版本,但这显然不是推荐的方法。

确保已编译的 *.class 文件存在于构建的 WAR 中

如果您使用的是 Eclipse 和/或 Maven 等构建工具,那么您需要绝对确保已编译的 servlet 类文件驻留在其包结构中,位于生成的 WAR 文件的 /WEB-INF/classes 文件夹中。在 package com.example; public class YourServlet 的情况下,它必须位于 /WEB-INF/classes/com/example/YourServlet.class 中。否则你将面临 @WebServlet 404 错误,或者 <servlet> HTTP 500 错误,如下所示:

HTTP 状态 500

实例化 servlet 类 com.example.YourServlet 时出错

并在服务器日志中找到 java.lang.ClassNotFoundException: com.example.YourServlet ,然后是 java.lang.NoClassDefFoundError: com.example.YourServlet ,然后依次是 jakarta.servlet.ServletException: Error instantiating servlet class com.example.YourServlet

验证 servlet 是否正确编译并放置在类路径中的一种简单方法是让构建工具生成一个 WAR 文件(例如,右键单击项目,在 Eclipse 中 _导出 > WAR 文件_),然后使用 ZIP 工具检查其内容。如果 servlet 类在 /WEB-INF/classes 中丢失,或者如果导出导致错误,则项目配置错误或一些 IDE/项目配置默认值被错误地恢复(例如, 项目 > 自动构建 已被禁用蚀)。

您还需要确保项目图标没有指示构建错误的红叉。您可以在“ _问题_”视图( “窗口”>“显示视图”>“其他…” )中找到确切的错误。通常错误信息是很好的 Googlable。如果您不知道,最好从头开始,不要触及任何 IDE/项目配置默认值。如果您使用的是 Eclipse,可以在 如何在我的 Eclipse 项目中导入 javax.servlet / jakarta.servlet API? 中找到说明。

在没有任何 JSP/HTML 页面的情况下单独测试 servlet

假设服务器运行在 localhost:8080 上,并且 WAR 成功部署在 /contextname 的上下文路径(默认为 IDE 项目名称,区分大小写!),以及 servlet尚未初始化失败(读取服务器日志以获取任何部署/servlet 成功/失败消息以及实际上下文路径和 servlet 映射),然后 URL 模式为 /servlet 的 servlet 可在 http://localhost:8080/contextname/servlet

您可以直接在浏览器的地址栏中输入它来单独测试它。如果它的 doGet() 被正确覆盖和实现,那么您将在浏览器中看到它的输出。或者,如果您没有任何 doGet() 或者它错误地调用了 super.doGet() ,则会显示“ HTTP 405:此 URL 不支持 HTTP 方法 GET ”错误(仍然比 404 好,因为 405 是实际找到 servlet 本身的证据)。

重写 service() 是一种不好的做法,除非你正在重新发明一个 MVC 框架——如果你刚开始使用 servlet 并且对当前问题中描述的问题一无所知,这是不太可能的;)请参阅还有 设计模式基于 Web 的应用程序

无论如何,如果 servlet 在单独测试时已经返回 404,那么尝试使用 HTML 表单是完全没有意义的。因此,从逻辑上讲,在有关来自 servlet 的 404 错误的问题中包含任何 HTML 表单也是完全没有意义的。

使用域相对 URL 从 HTML 引用 servlet

一旦您验证了 servlet 在单独调用时工作正常,您就可以继续使用 HTML。至于 HTML 表单的具体问题, <form action> 值需要是有效的 URL。这同样适用于 <a href><img src><script src> 等。您需要了解绝对/相对 URL 的工作原理。您知道,URL 是您可以在网络浏览器的地址栏中输入/查看的网址。如果您将相对 URL 指定为表单操作,即没有 http:// 方案,那么它将变成相对于您在网络浏览器地址栏中看到的 当前 URL。因此,它与许多初学者似乎认为的服务器 WAR 文件夹结构中的 JSP/HTML 文件位置绝对无关。

因此,假设带有 HTML 表单的 JSP 页面由 http://localhost:8080/contextname/jsps/page.jsp 打开(因此 不是 file://... ),并且您需要提交到位于 http://localhost:8080/contextname/servlet 的 servlet --- , here are several cases (note that you can here safely substitute <form action> with <a href> , <img src> , <script src> , etc):

  • 表单操作提交到带有前导斜杠的 URL。
     <form action="/servlet">

前导斜杠 / 使 URL 相对于域,因此表单将提交到

    http://localhost:8080/servlet

但这可能会导致 404,因为它处于错误的上下文中。


  • 表单操作提交到没有前导斜杠的 URL。
     <form action="servlet">

这使得 URL 相对于当前 URL 的当前文件夹,因此表单将提交到

    http://localhost:8080/contextname/jsps/servlet

但这可能会导致 404,因为它位于错误的文件夹中。


  • 表单操作提交到一个向上一个文件夹的 URL。
     <form action="../servlet">

这将向上移动一个文件夹(就像在本地磁盘文件系统路径中一样!),因此表单将提交给

    http://localhost:8080/contextname/servlet

这个必须工作!


  • 然而,规范的方法是使 URL 成为域相关的,这样当您碰巧将 JSP 文件移动到另一个文件夹时就不需要再次修复 URL。
     <form action="${pageContext.request.contextPath}/servlet">

这将产生

    <form action="/contextname/servlet">

因此,它将始终提交到正确的 URL。


在 HTML 属性中使用直引号

You need to make absolutely sure you’re using straight quotes in HTML attributes like action="..." or action='...' and thus not curly quotes like action=”...” or action=’...’ 。 HTML 不支持大引号,它们只会成为值的一部分。从博客中复制粘贴代码片段时要小心!一些博客引擎,尤其是 Wordpress,默认使用所谓的“智能引号”,因此也会以这种方式破坏代码片段中的引号。另一方面,不要复制粘贴代码,而是尝试自己简单地键入代码。通过你的大脑和手指实际获取代码的另一个好处是,从长远来看,它会让你更好地记住和理解代码,也会让你成为更好的开发人员。

也可以看看:

HTTP Status 404 错误的其他情况:

原文由 BalusC 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏