【问题标题】:HTTP Server - Serving up favicon.icoHTTP 服务器 - 提供 favicon.ico
【发布时间】:2017-12-20 20:40:08
【问题描述】:

我正在尝试设置我自己的 java http 服务器,以更好地了解 http 服务器以及网络底层发生的事情。我开发了一个非常简单的服务器,并且能够同时提供 html 页面和 JSON 格式的数据。然后我看到浏览器(我使用的是 chrome,但假设其他浏览器也一样)正在发送对 favicon.ico 的请求。我能够在我的服务器上识别该请求,因此我尝试提供一个随机图标,我下载并调整为 png 格式的 16x16 像素,因为这就是互联网所说的大小需要。这是我的代码,注意它不应该是任何专业的东西,只是对我的基本教育目的有用的东西:

[set up ServerSocket and listen]
public static String err_header = "HTTP/1.1 500 ERR\nAccess-Control-Allow-Origin: *";
public static String success_header = "HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *";
public static String end_header = "\r\n\r\n";
while(true){
        try{
            System.out.println("Listening for new connections");
            clientSocket = server.accept();
            System.out.println("Connection established");
            InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream());
            BufferedReader reader = new BufferedReader(isr);
            String getLine = reader.readLine();//first line of HTTP request
            handleRequest(getLine,clientSocket);
        }//end of try
        catch(Exception e){
            [error stuff]
        }//end of catch
    }//end of while

HandleRequest 方法:

public static void handleRequest(String getLine,Socket clientSocket) throws Exception{
    if(getLine.substring(5,16).equals("favicon.ico")){
        List<String> iconTag = new ArrayList<String>();
        iconTag.add("\nContent-Type: image/png");
        handleFileRequest("[file]",iconTag,clientSocket);
    }//end of if
    else{
        handleFileRequest("[file]",clientSocket);
    }//end of else
}//end of handleRequest

处理图片的文件请求:

public static void handleFileRequest(String fileName,List<String> headerTags,Socket clientSocket) throws Exception{
    OutputStream out = clientSocket.getOutputStream();
    BufferedReader read = new BufferedReader(new FileReader(fileName));
    out.write(success_header.getBytes("UTF-8"));
    Iterator<String> itr = headerTags.iterator();
    while(itr.hasNext()){
        out.write(itr.next().getBytes("UTF-8"));
    }//end of while
    out.write(end_header.getBytes("UTF-8"));
    String readLine = "";
    while((readLine = read.readLine())!=null){
        out.write(readLine.getBytes("UTF-8"));
    }//end of while
    out.flush();
    out.close();
}//end of handleFileRequest

它似乎可以工作,当服务器发送文件时,浏览器显示 200 OK 响应,但没有网站图标,当我将网络请求过滤为仅图像时,正在提供的页面请求了一个图像,但是favicon 请求未在此处列出(favicon 请求位于“其他”部分)。同样,当单击其他图像时,图像会显示在预览中,而 favicon 请求并非如此。截图:

同时这是另一张图片的样子,它在页面中显示得很好:

我也尝试包含 Content-Length 标头,但这似乎没有什么不同。我错过了什么明显的东西吗?

另外澄清一下,我知道我可以在实际的 html 页面中包含 favicon,目标不是这样做,而是要了解它是如何工作的。

【问题讨论】:

  • 如果直接访问favicon的url,看到了吗?
  • 没有 url,我通过 http 将其作为字节发送。
  • 不确定你的意思。您可以在屏幕截图的 HTTP 请求标头中看到网站图标的 url。浏览器正在请求它。这就是浏览器通过发送 HTTP 请求来获取网站图标的方式。如果网站图标服务正确,那么如果您直接访问其网址,您应该能够看到它。
  • 那么单独的http请求是怎么产生的?它向服务器发出请求以获取数据。我的服务器回复了适当的 HTTP 标头,后跟图标的二进制文件,而不是链接。适当的响应是否实际上只是 url,然后 chrome 会对该链接发出额外的 http 请求?
  • 我去了那个截图中的网址。响应字段填充了适当的二进制文件,但图像未正确显示在浏览器中。

标签: java http web server


【解决方案1】:

读取二进制文件

网站图标的内容似乎没有正确提供。

我怀疑这很可能是由于您阅读其内容的方式:

while((readLine = read.readLine())!=null){
    out.write(readLine.getBytes("UTF-8"));
}

逐行读取二进制内容是不合适的, 因为行的概念,还有 UTF-8 编码, 在二进制文件的上下文中没有意义。 而且您无法以这种方式逐行正确读取二进制内容, 因为BufferedReaderreadLine 方法不会返回整行,因为它会从末尾删除换行符。 您无法手动添加换行符,因为您无法知道它到底是什么。

这是读取二进制文件内容的更简单且正确的方法:

byte[] bytes = Files.readAllBytes(Paths.get("/path/to/file"));

一旦你有了这个,就很容易使用bytes.length的值生成一个正确的文件头和内容长度。

当您在浏览器中访问页面时会发生什么

如果我们澄清一些事情,似乎对你有好处。

当您在浏览器中打开 URL 时, 浏览器向 Web 服务器发送 GET 请求以下载您指定的原始 URL 的内容。

一旦有了页面内容,它会进一步发送 GET 请求:

  • 如果还没有图标,请获取图标。这个位置可以在 HTML 文档中指定,否则浏览器会默认尝试获取SERVERNAME/favicon.ico
  • 获取文档中任意(有效)&lt;img/&gt; 标记的src 属性中指定的图像
  • 获取文档中任意(有效)&lt;style/&gt; 标记的href 属性中指定的样式表
  • ...同样适用于&lt;script/&gt;标签,依此类推...

图标纯粹是装饰性的,显示在浏览器标签标题中, 其他资源对于呈现页面至关重要。 它们在 lynx 等基于文本的浏览器中不是必需的, 这样的浏览器显然不会获取这些资源。

这是为什么请求网站图标以及如何请求的说明。

Web 服务器如何提供文件?

在最基本的情况下,提供文件有两个重要的组成部分:

  1. 产生一个合适的HTTP头:头中的每一行都是name: value格式,每一行必须以\n结尾。 必须至少有一个 Content-type 标头。 标题必须以空行结束。

  2. 在终止标题的空行之后, 内容可以是任何东西,甚至是二进制的。 举例说明, 考虑curl 命令,它将url 的内容转储到标准输出。 如果你运行curl url-to-some-html-file, 您将看到 html 文件的内容。 如果你运行curl url-to-some-image-file, 您将看到图像文件的内容。 这将是不可读的,并且您的终端可能会发出有趣的声音。 您可以使用curl url-to-some-image-file &gt; image.png 将输出重定向到一个文件, 这会给你一个图像文件, 二进制内容, 可以在任何图像查看器工具中打开。

简而言之,提供文件实际上只是在stdout 上打印一个标题, 然后打印一个空行来终止标题, 然后将内容打印到stdout

调试图片服务

调试图像是否正确提供的一种简单方法是使用 curl 将 URL 保存到文件中, 然后验证保存的文件和原始文件是否相同, 例如使用cmp 命令:

curl -o file url-to-favicon
cmp file /path/to/original

cmp 的输出应该为空。 此命令仅在发现两个文件存在差异时才会产生输出。

实现一个简单的 HTTP 服务器

而不是使用ServerSocket, 这是实现 HTTP 服务器的一种极其简单的方法:

HttpServer server = HttpServer.create(new InetSocketAddress(1234), 0);

server.createContext("/favicon.ico", t -> {
  byte[] bytes = Files.readAllBytes(Paths.get("/path/to/favicon"));
  t.sendResponseHeaders(200, bytes.length);
  try (OutputStream os = t.getResponseBody()) {
    os.write(bytes);
  }
});

server.createContext("/", t -> {
  Charset charset = StandardCharsets.UTF_8;
  List<String> lines = Files.readAllLines(Paths.get("/path/to/index"), charset);

  t.sendResponseHeaders(200, 0);

  try (OutputStream os = t.getResponseBody()) {
    for (String line : lines) {
      os.write((line + "\n").getBytes(charset));
    }
  }
});

server.start();

【讨论】:

  • 这很有意义,我假设我的逻辑也在 HTML 页面中丢失换行符,但由于这不会改变内容,我没有注意到它。通过此更改,它可以正确计算字节数,现在浏览器将其视为 png 文件,因此这是一个改进,但它仍然没有在 favicon 位置或 chrome 开发人员工具的预览中显示实际图像(当我导航到 url 时,实际图标不显示)。
  • @zachvac 我添加了更多解释(请参阅我的帖子中间的几个新部分)以帮助您调试。
  • 谢谢,这行得通:) 它与编码有关,因为我正在做的是获取字节数组,创建一个新字符串,然后调用 getBytes("UTF-8")在上面。我将研究该行实际上在做什么,但是当我刚刚发送有效的原始字节数组并且图像按预期显示时。非常感谢您的所有帮助:)
猜你喜欢
  • 2021-05-01
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 2020-09-01
  • 2017-04-26
  • 2014-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多