C#实现HTTP服务器:(2)向客户端发送Hello World

完整项目托管地址:https://github.com/hooow-does-it-work/http

前面文章实现了HTTP请求头的解析:C#实现HTTP服务器:(1)解析HTTP请求头
现在我们尝试向浏览器发送一些内容。
本文使用Content-Length发送固定长度内容的响应数据,后面会写文使用Chunked方式发送数据。

修改前面文章NewClient重载的内容如下。

public class HttpServer : TcpIocpServer
{
    protected override void NewClient(Socket client)
    {
        Stream stream = new NetworkStream(client, true);

        //捕获一个HttpRequest
        HttpRequest request = HttpRequest.Capture(stream);

        //实例化HttpResponse,方便管理HTTP响应头
        HttpResponse response = new HttpResponse(200);

        //需要发送到客户端的内容
        string responseText = "<p>hello world!</p>";

        //内容对应的二进制数据
        byte[] responseBody = Encoding.ASCII.GetBytes(responseText);

        //设置Content-Length请求头,大小为响应内容的二进制数据长度
        //由于HTTP/1.1协议的限制,客户端必须有明确的界定条件,来从传输中读取正确的内容
        //一种方法就是,设置Content-Length头,客户端只要读取指定长度的数据即可。
        //再一种方法就是,使用Chunked,进行分块传输,用一个结束快告诉客户端,数据传输完毕了
        //还有就是,服务器发送完数据后,可以直接关闭连接,客户端读完全部数据。
        //
        //对于一些不需要响应内容的code,可以不设置。例如204 No Content
        response.SetHeader("Content-Length", responseBody.Length.ToString());

        //设置响应内容类型,这里设置为text/html
        response.SetHeader("Content-Type", "text/html");

        //告诉浏览器,本次连接为非长链,服务器会关闭连接。
        //HTTP/1.1允许长链接,可以在一个连接上发送多次请求和响应。
        response.SetHeader("Connection", "close");

        //获取整个需要发送给客户端的响应头
        string responseHeaders = response.GetAllResponseHeaders();
        byte[] responseHeaderBuffer = Encoding.ASCII.GetBytes(responseHeaders);

        //发送响应头
        stream.Write(responseHeaderBuffer, 0, responseHeaderBuffer.Length);

        //发送响应内容
        stream.Write(responseBody, 0, responseBody.Length);

        stream.Close();
    }
}

服务器启动成功后,浏览器访问:http://127.0.0.1:4189/
浏览器响应
我们可以将浏览器请求的信息,发回给浏览器,看看我们解析的数据和浏览器发送的是否一致。
将responseText部分代码修改为下面内容。

 //需要发送到客户端的内容
 string responseText = $"<p>hello world!</p><pre>{request.GetAllRequestHeaders()}</pre>";

浏览器访问:http://127.0.0.1:4189/,响应如下图。
可以看到,我们解析的内容,跟浏览器发送的Request Headers完全一致。
Response Headers也是我们在代码中设置的响应头。
浏览器响应

这里我们实现了使用Content-Length向浏览器发送数据。
下篇文章,我们将使用Chunked方式,发送数据到浏览器。

HttpResponse类源码
public class HttpResponse
{
    private int _statusCode = 200;
    private string _statusText = "OK";
    private string _httpProtocol = null;

    private NameValueCollection _headers = new NameValueCollection();

    public NameValueCollection Headers => _headers;

    public int StatusCode { 
        get => _statusCode; 
        set {
            string statusText = HttpStatus.GetStatus(value);

            if (string.IsNullOrEmpty(statusText))
                throw new Exception($"未知状态码:{value}");

            _statusCode = value;
            _statusText = statusText;
        }
    }

    internal HttpResponse() { }

    /// <summary>
    /// 使用状态码和协议实例化一个HttpResponse类
    /// </summary>
    /// <param name="statusCode">状态码,200、400等</param>
    /// <param name="httpProtocol">协议,默认用HTTP/1.1</param>
    public HttpResponse(int statusCode, string httpProtocol = "HTTP/1.1")
    {
        StatusCode = statusCode;
        _httpProtocol = httpProtocol;
    }

    /// <summary>
    /// 添加响应头
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="value">值</param>
    public void AddHeader(string name, string value)
    {
        _headers.Add(name, value);
    }

    /// <summary>
    /// 设置响应头
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="value">值</param>
    public void SetHeader(string name, string value)
    {
        _headers.Set(name, value);
    }

    /// <summary>
    /// 删除响应头
    /// </summary>
    /// <param name="name">名称</param>
    public void RemoveHeader(string name)
    {
        _headers.Remove(name);
    }


    /// <summary>
    /// 获获取完整的响应头
    /// </summary>
    /// <returns>完整响应头</returns>
    public string GetAllResponseHeaders()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0} {1} {2}\r\n", _httpProtocol, _statusCode, _statusText);

        foreach (string name in _headers.Keys)
        {
            string[] values = _headers.GetValues(name);
            if (values == null || values.Length == 0) continue;

            foreach (string value in values)
            {
                if (value == null) continue;
                sb.AppendFormat("{0}: {1}\r\n", name, value);
            }
        }
        sb.Append("\r\n");

        return sb.ToString();
    }
}
HttpStatus类源码

可以从statusCode获取statusText。


public class HttpStatus
{
    private static readonly string[][] s_HTTPStatusDescriptions;
    private static Dictionary<int, string> m_Cached = new Dictionary<int, string>();
    static HttpStatus()
    {
        s_HTTPStatusDescriptions = new string[6][];

        s_HTTPStatusDescriptions[1] = new string[] {
            "Continue", "Switching Protocols", "Processing"
        };

        s_HTTPStatusDescriptions[2] = new string[] {
            "OK", "Created", "Accepted", "Non-Authoritative Information", "No Content", "Reset Content", "Partial Content", "Multi-Status"
        };

        s_HTTPStatusDescriptions[3] = new string[] {
            "Multiple Choices", "Moved Permanently", "Found", "See Other", "Not Modified", "Use Proxy", string.Empty, "Temporary Redirect"
        };

        s_HTTPStatusDescriptions[4] = new string[] {
            "Bad Request", "Unauthorized", "Payment Required", "Forbidden", "Not Found", "Method Not Allowed", "Not Acceptable",
            "Proxy Authentication Required", "Request Timeout", "Conflict", "Gone", "Length Required", "Precondition Failed",
            "Request Entity Too Large", "Request-Uri Too Long", "Unsupported Media Type",
            "Requested Range Not Satisfiable", "Expectation Failed", string.Empty, string.Empty, string.Empty, string.Empty,
            "Unprocessable Entity", "Locked", "Failed Dependency"
        };

        s_HTTPStatusDescriptions[5] = new string[] { "Internal Server Error", "Not Implemented", "Bad Gateway", "Service Unavailable",
            "Gateway Timeout", "Http Version Not Supported", string.Empty, "Insufficient Storage"
        };

    }

    public static string GetStatus(int code)
    {
        if (m_Cached.ContainsKey(code)) return m_Cached[code];

        int main = code / 100;
        int sub = code % 100;
        if (main <= 0 || main > 5)
        {
            m_Cached[code] = "";
            return "";
        }

        string[] texts = s_HTTPStatusDescriptions[main];
        if (sub < 0 || sub >= texts.Length)
        {
            m_Cached[code] = "";
            return "";
        }
        m_Cached[code] = texts[sub];
        return texts[sub];
    }
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anlige

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值