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.5/9 中如下:

<块引用>

HTTP 状态 404 - 未找到

消息:/servlet

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

或者在Tomcat 10中如下:

<块引用>

HTTP 状态 404 - 未找到

类型:状态报告

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

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

为什么它不起作用?

解决方案

简介

这可能有很多原因,分为以下几节:

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

将servlet类放入

首先,将servlet 类放入Java .您应该始终将可公开重用的 Java 类放入包中,否则它们对于包中的类(例如服务器本身)是不可见的.通过这种方式,您可以消除潜在的特定环境问题.无包 servlet 只能在特定的 Tomcat+JDK 组合中工作,永远不要依赖这一点.

在普通"的情况下IDE项目中,类需要放在其包结构里面的Java Sources"里面.文件夹,不是在Web Content"中文件夹,用于存放 JSP 等 Web 文件.下面是在 Navigator 视图中看到的默认 Eclipse Dynamic Web Project 的文件夹结构示例(Java Sources"文件夹默认位于此类项目中,由src 文件夹):

EclipseProjectName|-- 源|`--com|`-- 例子|`-- YourServlet.java|-- 网页内容||-- 网络信息||`-- web.xml|`--jsps|`--page.jsp:

如果是 Maven 项目,则需要将类放在 main/java 内的包结构中,因此 not main/resources, - 包含一些 hello world 示例

  • 如何从 HTML 表单调用 servlet 类
  • 在 Servlet 中做获取和做发布
  • 如何通过单击 JSP 页面中的超链接或按钮将当前项传递给 Java 方法?
  • 其他HTTP状态404错误情况:

    • HTTP 状态 404 - Servlet [ServletName] 不可用
    • HTTP 状态 404 - 请求的资源 (/ProjectName/) 不可用
    • HTTP 状态 404 - 请求的资源 (/) 不可用
    • /WEB-INF 中的 JSP 返回HTTP 状态 404 请求的资源不可用"
    • 在 JSP 文件中引用放置在 WEB-INF 文件夹中的资源会在资源上返回 HTTP 404
    • 在调用转发到 JSP 的 Servlet 时,浏览器无法访问/查找 CSS、图像和链接等相关资源

    I have an HTML form in a JSP file in my WebContent/jsps folder. I have a servlet class servlet.java in my default package in src folder. In my web.xml it is mapped as /servlet.

    I have tried several URLs in action attribute of the HTML form:

    <form action="/servlet">
    

    <form action="/servlet.java">
    

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

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

    But none of those work. They all keep returning a HTTP 404 error like below in Tomcat 6/7/8:

    HTTP Status 404 — /servlet

    Description: The requested resource (/servlet) is not available.

    Or as below in Tomcat 8.5/9:

    HTTP Status 404 — Not Found

    Message: /servlet

    Description: The origin server did not find a current representation for the target resource or is not willing to disclose that one exists

    Or as below in Tomcat 10:

    HTTP Status 404 — Not Found

    Type: Status Report

    Message: The requested resource (/servlet) is not available

    Description: The origin server did not find a current representation for the target resource or is not willing to disclose that one exists

    Why is it not working?

    解决方案

    Introduction

    This can have a lot of causes which are broken down in following sections:

    • Put servlet class in a package
    • Set servlet URL in url-pattern
    • @WebServlet works only on Servlet 3.0 or newer
    • javax.servlet.* doesn't work anymore in Servlet 5.0 or newer
    • Make sure compiled *.class file is present in built WAR
    • Test the servlet individually without any JSP/HTML page
    • Use domain-relative URL to reference servlet from HTML
    • Use straight quotes in HTML attributes

    Put servlet class in a package

    First of all, put the servlet class in a Java package. You should always put publicly reuseable Java classes in a package, otherwise they are invisible to classes which are in a package, such as the server itself. This way you eliminate potential environment-specific problems. Packageless servlets work only in specific Tomcat+JDK combinations and this should never be relied upon.

    In case of a "plain" IDE project, the class needs to be placed in its package structure inside the "Java Sources" folder, not inside "Web Content" folder, which is for web files such as JSP. Below is an example of the folder structure of a default Eclipse Dynamic Web Project as seen in Navigator view (the "Java Sources" folder is in such project by default represented by src folder):

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

    In case of a Maven project, the class needs to be placed in its package structure inside main/java and thus not main/resources, this is for non-class files and absolutely also not main/webapp, this is for web files. Below is an example of the folder structure of a default Maven webapp project as seen in Eclipse's Navigator view:

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

    Note that the /jsps subfolder is not strictly necessary. You can even do without it and put the JSP file directly in webcontent/webapp root, but I'm just taking over this from your question.

    Set servlet URL in url-pattern

    The servlet URL is specified as the "URL pattern" of the servlet mapping. It's absolutely not per definition the classname/filename of the servlet class. The URL pattern is to be specified as value of @WebServlet annotation.

    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.
        // ...
    }
    

    In case you want to support path parameters like /servlet/foo/bar, then use an URL pattern of /servlet/* instead. See also Servlet and path parameters like /xyz/{value}/test, how to map in web.xml?

    @WebServlet works only on Servlet 3.0 or newer

    In order to use @WebServlet, you only need to make sure that your web.xml file, if any (it's optional since Servlet 3.0), is declared conform Servlet 3.0+ version and thus not conform e.g. 2.5 version or lower. Below is a Servlet 5.0 compatible one (which matches Tomcat 10+, WildFly 22+ (Preview), GlassFish/Payara 6+, etc).

    <?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>
    

    And below is a Servlet 4.0 compatible one (which matches Tomcat 9+, WildFly 11+, GlassFish/Payara 5+, etc).

    <?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>
    

    Or, in case you're not on Servlet 3.0+ yet (e.g. Tomcat 6 or older), then remove the @WebServlet annotation.

    package com.example;
    
    import javax.servlet.http.HttpServlet;
    
    public class YourServlet extends HttpServlet {
        // ...
    }
    

    And register the servlet instead in web.xml like this:

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

    Note thus that you should not use both ways. Use either annotation based configuarion or XML based configuration. When you have both, then XML based configuration will override annotation based configuration.

    javax.servlet.* doesn't work anymore in Servlet 5.0 or newer

    Since Jakarta EE 9 / Servlet 5.0 (Tomcat 10, TomEE 9, WildFly 22 Preview, GlassFish 6, Payara 6, Liberty 22, etc), the javax.* package has been renamed to jakarta.* package.

    In other words, please make absolutely sure that you don't randomly put JAR files of a different server in your WAR project such as tomcat-servlet-api-9.x.x.jar merely in order to get the javax.* package to compile. This will only cause trouble. Remove it altogether and edit the imports of your servlet class from

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

    to

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

    In case you're using Maven, you can find examples of proper pom.xml declarations for Tomcat 10+, Tomcat 9-, JEE 9+ and JEE 8- in this answer: Tomcat casting servlets to javax.servlet.Servlet instead of jakarta.servlet.http.HttpServlet. The alternative is to downgrade the server to an older version, e.g. from Tomcat 10 back to Tomcat 9 or older, but this is clearly not the recommended way to go.

    Make sure compiled *.class file is present in built WAR

    In case you're using a build tool such as Eclipse and/or Maven, then you need to make absolutely sure that the compiled servlet class file resides in its package structure in /WEB-INF/classes folder of the produced WAR file. In case of package com.example; public class YourServlet, it must be located in /WEB-INF/classes/com/example/YourServlet.class. Otherwise you will face in case of @WebServlet also a 404 error, or in case of <servlet> a HTTP 500 error like below:

    HTTP Status 500

    Error instantiating servlet class com.example.YourServlet

    And find in the server log a java.lang.ClassNotFoundException: com.example.YourServlet, followed by a java.lang.NoClassDefFoundError: com.example.YourServlet, in turn followed by jakarta.servlet.ServletException: Error instantiating servlet class com.example.YourServlet.

    An easy way to verify if the servlet is correctly compiled and placed in classpath is to let the build tool produce a WAR file (e.g. rightclick project, Export > WAR file in Eclipse) and then inspect its contents with a ZIP tool. If the servlet class is missing in /WEB-INF/classes, or if the export causes an error, then the project is badly configured or some IDE/project configuration defaults have been mistakenly reverted (e.g. Project > Build Automatically has been disabled in Eclipse).

    You also need to make sure that the project icon has no red cross indicating a build error. You can find the exact error in Problems view (Window > Show View > Other...). Usually the error message is fine Googlable. In case you have no clue, best is to restart from scratch and do not touch any IDE/project configuration defaults. In case you're using Eclipse, you can find instructions in How do I import the javax.servlet / jakarta.servlet API in my Eclipse project?

    Test the servlet individually without any JSP/HTML page

    Provided that the server runs on localhost:8080, and that the WAR is successfully deployed on a context path of /contextname (which defaults to the IDE project name, case sensitive!), and the servlet hasn't failed its initialization (read server logs for any deploy/servlet success/fail messages and the actual context path and servlet mapping), then a servlet with URL pattern of /servlet is available at http://localhost:8080/contextname/servlet.

    You can just enter it straight in browser's address bar to test it invidivually. If its doGet() is properly overriden and implemented, then you will see its output in browser. Or if you don't have any doGet() or if it incorrectly calls super.doGet(), then a "HTTP 405: HTTP method GET is not supported by this URL" error will be shown (which is still better than a 404 as a 405 is evidence that the servlet itself is actually found).

    Overriding service() is a bad practice, unless you're reinventing a MVC framework — which is very unlikely if you're just starting out with servlets and are clueless as to the problem described in the current question ;) See also Design Patterns web based applications.

    Regardless, if the servlet already returns 404 when tested invidivually, then it's entirely pointless to try with a HTML form instead. Logically, it's therefore also entirely pointless to include any HTML form in questions about 404 errors from a servlet.

    Use domain-relative URL to reference servlet from HTML

    Once you've verified that the servlet works fine when invoked individually, then you can advance to HTML. As to your concrete problem with the HTML form, the <form action> value needs to be a valid URL. The same applies to <a href>, <img src>, <script src>, etc. You need to understand how absolute/relative URLs work. You know, an URL is a web address as you can enter/see in the webbrowser's address bar. If you're specifying a relative URL as form action, i.e. without the http:// scheme, then it becomes relative to the current URL as you see in your webbrowser's address bar. It's thus absolutely not relative to the JSP/HTML file location in server's WAR folder structure as many starters seem to think.

    So, assuming that the JSP page with the HTML form is opened by http://localhost:8080/contextname/jsps/page.jsp (and thus not by file://...), and you need to submit to a servlet located in http://localhost:8080/contextname/servlet, here are several cases (note that you can here safely substitute <form action> with <a href>, <img src>, <script src>, etc):

    • Form action submits to an URL with a leading slash.

        <form action="/servlet">
      

      The leading slash / makes the URL relative to the domain, thus the form will submit to

        http://localhost:8080/servlet
      

      But this will likely result in a 404 as it's in the wrong context.


    • Form action submits to an URL without a leading slash.

        <form action="servlet">
      

      This makes the URL relative to the current folder of the current URL, thus the form will submit to

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

      But this will likely result in a 404 as it's in the wrong folder.


    • Form action submits to an URL which goes one folder up.

        <form action="../servlet">
      

      This will go one folder up (exactly like as in local disk file system paths!), thus the form will submit to

        http://localhost:8080/contextname/servlet
      

      This one must work!


    • The canonical approach, however, is to make the URL domain-relative so that you don't need to fix the URLs once again when you happen to move the JSP files around into another folder.

        <form action="${pageContext.request.contextPath}/servlet">
      

      This will generate

        <form action="/contextname/servlet">
      

      Which will thus always submit to the right URL.


    Use straight quotes in HTML attributes

    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=’...’. Curly quotes are not supported in HTML and they will simply become part of the value. Watch out when copy-pasting code snippets from blogs! Some blog engines, notably Wordpress, are known to by default use so-called "smart quotes" which thus also corrupts the quotes in code snippets this way. On the other hand, instead of copy-pasting code, try simply typing over the code yourself. Additional advantage of actually getting the code through your brain and fingers is that it will make you to remember and understand the code much better in long term and also make you a better developer.

    See also:

    • Our servlets wiki page - Contains some hello world examples
    • How to call servlet class from HTML form
    • doGet and doPost in Servlets
    • How do I pass current item to Java method by clicking a hyperlink or button in JSP page?

    Other cases of HTTP Status 404 error:

    • HTTP Status 404 - Servlet [ServletName] is not available
    • HTTP Status 404 - The requested resource (/ProjectName/) is not available
    • HTTP Status 404 - The requested resource (/) is not available
    • JSP in /WEB-INF returns "HTTP Status 404 The requested resource is not available"
    • Referencing a resource placed in WEB-INF folder in JSP file returns HTTP 404 on resource
    • Browser can't access/find relative resources like CSS, images and links when calling a Servlet which forwards to a JSP

    相关文章