我正在使用套接字在Java中开发一个简单的HTTP Web服务器.我目前正在开发一项功能,该功能允许我自动呈现静态文件.一切都很好,我可以渲染CSS和JavaScript文件,但由于某种原因,我无法渲染任何图像文件.

打印响应头确实会返回图像字节.当我在调试时查看网络选项卡时,它显示映像路由返回了大约381kB的数据,而映像本身是151kB,所以我假设有什么东西正在返回.

当我转到图像路径时,它显示"图像(目录)无法显示,因为它包含错误."

MochaResponse.java

package com.gabrielgavrilov.mocha;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class MochaResponse
{

    public StringBuilder header = new StringBuilder();

    MochaResponse(String status, String contentType)
    {
        this.header.append("HTTP/1.0 " + status + "\r\n");
        this.header.append("Content-Type: " + contentType + "\r\n");
        appendEmpty();
    }

    public void addHeader(String header, String value)
    {
        this.header.append(header + ": " + value + "\r\n");
    }

    public void setCookie(String key, String value)
    {
        addHeader("Set-Cookie", key+"="+value);
    }

    public void send(String data)
    {
        this.header.append(data);
        appendEmpty();
    }

    public void render(String fileName)
    {
        render(fileName, Mocha.VIEWS_DIRECTORY);
    }

    public void render(String fileName, String directory)
    {
        try
        {
            String fileContent = Files.readString(Paths.get(directory + fileName));
            send(fileContent);
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    protected void renderImage(String file)
    {
        try
        {
            FileInputStream content = new FileInputStream(Mocha.STATIC_DIRECTORY + file);

            int i = 0;
            while((i = content.read()) != -1)
            {
                this.header.append(i);
            }

            System.out.println(this.header);

            appendEmpty();
            content.close();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }

    private void appendEmpty()
    {
        this.header.append("\r\n");
    }

}

MochaClient.java

package com.gabrielgavrilov.mocha;

import java.io.*;
import java.nio.Buffer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class MochaClient {

    MochaClient(InputStream clientInput, OutputStream clientOutput)
    {
        try
        {
            InputStreamReader sr = new InputStreamReader(clientInput);
            BufferedReader br = new BufferedReader(sr);
            StringBuilder clientHeader = new StringBuilder();

            String line;
            while((line = br.readLine()).length() != 0)
            {
                clientHeader.append(line + "\r\n");
            }

            String route = getRequestedRoute(clientHeader.toString());
            String method = getRequestedMethod(clientHeader.toString());

            handleRequest(clientHeader.toString(), route, method, clientOutput, br);
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    private void handleRequest(String header, String route, String method, OutputStream clientOutput, BufferedReader br) throws IOException
    {
        String type = checkForStaticRoute(route);

        if(type != null)
        {
            handleStaticRoute(route, type, clientOutput);
            return;
        }

        switch(method)
        {
            case "GET":
                handleGetRequest(header, route, clientOutput);
                break;
            case "POST":
                handlePostRequest(header, route, clientOutput, br);
                break;
        }
    }

    private String checkForStaticRoute(String route) throws IOException
    {
        if(route.contains("."))
        {
            String[] routeSplit = route.split("\\.");
            return routeSplit[routeSplit.length-1];
        }

        return null;
    }

    private void handleStaticRoute(String route, String type, OutputStream clientOutput) throws IOException
    {
        switch(type)
        {
            case "css":
                renderStaticFile("text/css", route, clientOutput);
                break;
            case "js":
                renderStaticFile("text/javascript", route, clientOutput);
                break;
            case "png":
                renderStaticImage("image/png", route, clientOutput);
                break;
            case "jpeg":
                renderStaticImage("image/jpeg", route, clientOutput);
                break;
        }
    }

    private void renderStaticFile(String contentType, String route, OutputStream clientOutput) throws IOException
    {
        String file = route.substring(1);
        MochaResponse response = new MochaResponse("200 OK", contentType);

        response.render(file, Mocha.STATIC_DIRECTORY);

        clientOutput.write(response.header.toString().getBytes());
        clientOutput.flush();
    }

    private void renderStaticImage(String contentType, String route, OutputStream clientOutput) throws IOException
    {
        String file = route.substring(1);
        MochaResponse response = new MochaResponse("200 OK", contentType);

        response.renderImage(file);

        clientOutput.write(response.header.toString().getBytes());
        clientOutput.flush();
    }

    ...

如果有人能帮我指引正确的方向,我将不胜感激. 谢谢!

推荐答案

你的MochaResponse级有很多问题.

  • 您使用StringBuilder来准备整个HTTP响应.这对于文本格式的html和css来说很好,但是图像是二进制数据,所以你不能用StringBuilder来发送它.可以考虑使用ByteArrayOutputStreamBufferedOutputStream.您可以在需要时使用PrintWriterOutputStreamWriter向其中写入文本数据.否则,使用StringBuilderonly来准备HTTP响应头(顾名思义),然后在HTTP响应体中单独发送目标文件的原始字节.

  • 在构造函数中,您将两个缺省行追加到header,然后存储第三个空行.这第三行有效地终止了您的HTTP响应头,导致您之后附加到header的所有内容都被视为HTTP响应正文的一部分,包括对addHeader()的后续调用.在向其添加完HTTP头之前,不要将空行放入header.而且,根本不要在HTTP正文内容之后放置空行.这不是终止HTTP消息的方式.

  • renderImage()中,您从图像文件中读取每个字节,并将该字节数值的文本表示形式存储到StringBuider中.例如,如果读取字节0xFF,则存储文本值"255".这就是为什么您的HTTP响应比图像文件的实际大小大得多,以及为什么(部分)浏览器认为图像数据无效(另一部分是因为上面的#1).

  • 虽然不是绝对必要的(因为您在发送响应后关闭套接字连接),但是您应该包括一个Content-Length HTTP头来指定HTTP响应正文的实际字节大小,无论您发送的是什么文件格式.

话虽如此,你的MochaClient级也有一个大问题.它期望HTTP响应仅是纯文本的,它根本不处理二进制数据.它只是读取分隔的文本行,直到服务器关闭连接.这是读取HTTP消息的错误方式.

一旦到达分隔HTTP标头和HTTP正文的空行,就必须停止阅读行.然后,您需要分析HTTP标头以确定:

  1. HTTP正文包含的数据类型(文本与二进制,等等).

  2. 如何阅读(例如,分块VS NOT,压缩VS NOT等).

  3. 如何终止响应(即,通过0大小的块、Content-Length标头或套接字断开连接).

只有这样,您才能相应地实际读入HTTP正文,直到到达正确请求的末尾.

也就是说,Java有自己的内置HttpServerHttpClient类,可以考虑使用它们来代替从头开始实现HTTP协议.

Java相关问答推荐

Selenium Java:无法访问IFRAME内部的元素

Java应用程序崩溃时试图读取联系人从电话

Java在模块化jar文件中找不到类,但是javap可以

转换为Biggram

ittext pdf延迟签名,签名无效

RxJava PublishSubject缓冲区元素超时

springboot start loge change

使用GridBagLayout正确渲染

Java:使用Class.cast()将对象转换为原始数组

JDK 21-为什么线程局部随机S nextInt不直接用Super.nextInt实现?

如何在EXCEL单元格中添加形状和文本

Java中将文本拆分为数字或十进制数字和字符串

如何使用Criteria Builder处理一对多关系中的空值?

使IntelliJ在导入时优先 Select 一个类或将另一个标记为错误

我无法在我的Spring Boot应用程序中导入CSV依赖项

控制器建议异常处理

HBox内部的左对齐按钮(如果重要的话,在页码内)

如何在单元测试中获得我的装饰Mapstruct映射器的实例?

如何在 WebSphere Application Server 内的托管线程上运行 BatchEE 作业(job)?

元音变音字符:如何在 Java 中将Á<0x9c>转换为Ü?