본문 바로가기

C#/근웹 연대기

c#으로 근본 없는 웹서버 개발기 5 : 응답하라 FAVICON.ICO

⚠WARNING
ASP.NET에 대한 포스팅이 아닙니다
나가실 문은 오른쪽 하단입니다

 

 

전편의 지루한 이론학습을 적당히 끊고 다시 코딩을 하며 몸둥아리에 학습시킬 필요가 있다.

이론과 실습이 적당히 버무려졌을 때 학습효과는 최상급이 된다는 것이 나의 지론이다.

그래서 대충 가지고 있는 이미지로 FAVICON을 알아서 만들어주는 사이트에서 만들고, 폴더에 넣은후에 학습한 이론을 토대로 소스를 작성 하였다.

우선 FAVCON 요청에 대한 분기가 필요했다.

while (true)
{
    Socket client = server.Accept();
    try
    {
        var dic = request(client);

        if (dic.ContainsKey("err"))
        {
            resForbadrequest(client, dic["err"]);
        }
        else if (dic["url"] == "/")
        {
            response(client);
        }
        else if (dic["url"] == "/favicon.ico") // Yes, favicon please!
        {
            response(client, dic["url"]);
        }
        else
        {
            resForbadrequest(client, "Something is wrong");
        }

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
    finally
    {
        client.Close();
    }
}

 

 

그리고 자주 쓰일거 같지만 옆으로 길어져서 거슬리는 것들을 다른 파일로 보냈다.

public static class Util{

    const string dateFormat = "ddd, dd MMM yyy HH':'mm':'ss 'GMT'";

    public static readonly CultureInfo culInfo = new CultureInfo("en-US");

    public static readonly string uTime = utcEng(DateTime.Now);

    public static readonly string sPath = Environment.CurrentDirectory;
   
    public static string utcEng(DateTime dt){
        return dt.ToUniversalTime().ToString(dateFormat, Util.culInfo);
    }
}

필드 생성에 var 키워드가 안되는 것은 조금... 불편하다. 또 한가지, 어차피 static클래스인데 const를 제외한 모든 맴버에 굳이 static을 넣어야 하는 수고스러움이 있다. 그러나 어쩔 수가 없다 그것이 c#의 규칙이니까..

그렇다 딱 소스를 봣을 때, 어떤 타입인지 알아 볼 수 있도록 하는것이 그들의 policy인듯 하다.

그렇다 소스가 커지고 여러사람이 건들어야 할 경우에는 타입지정에 엄격해야 한다.

그렇다 디버그에 걸리는 시간은 소스의 길이의 제곱에 비례한다.

 

debug Time  =  source.Length ^ 2

 

버그가 안나게 하는것이 가장 좋겠지만, 혼자서 다 해먹는 소스코드에서도 생기는것이 다른사람과 섞을 때의 파급효과는 MS같은 거대기업의 직원들은 몸사리치게 잘 알것이다.

왠지readonly라는 키워드가 생긴 일화에는.. fucK*ing 이 변수를 좀 건들이지좀말라고! 같은 에피소드가 있었을듯 하다.

...

파비콘에 대한 응답을, 그리고 안좋은 요청에 대한 응답을 하는부분을 각각 작성하였다.

public static void response(Socket client, String url)
{
    string file = Util.sPath + "/public" + url;
    var info = new FileInfo(file);

    StringBuilder sb = new StringBuilder(100);
    sb.AppendLine("HTTP/1.1 200 ok");
    sb.AppendLine("Accept-Ranges: none");
    sb.AppendLine("Cache-Control: public, max-age=0");
    sb.AppendLine("Last-Modified: " + Util.utcEng(info.LastWriteTime));
    sb.AppendLine("Etag: " + DateTime.UtcNow.Ticks.ToString("x2"));
    sb.AppendLine("Date: " + Util.uTime);
    sb.AppendLine("Content-type:image/x-icon");
    sb.AppendLine("Server: test server");
    sb.AppendLine("Content-Length: " + info.Length);
    sb.AppendLine("Connection: keep-alive");
    sb.AppendLine("Keep-alive: timeout=5");
    sb.AppendLine();

    var headar = Encoding.UTF8.GetBytes(sb.ToString());
    client.Send(headar);
    client.Send(File.ReadAllBytes(info.FullName));
}

public static void resForbadrequest(Socket client, string msg)
{
    StringBuilder sb = new StringBuilder(100);
    sb.AppendLine("HTTP/1.1 400 Bad Request");
    sb.AppendLine("date: " + Util.uTime);
    sb.AppendLine("Server: test server");
    sb.AppendLine("Content-type:text/plain; charset=UTF-8");
    sb.AppendLine("Content-Length: " + msg.Length);
    sb.AppendLine("Connection: close");
    sb.AppendLine();
    sb.AppendLine(msg);

    var bytes = Encoding.UTF8.GetBytes(sb.ToString());
    client.Send(bytes);
}

Etag 와 Last-Modified는 차후에 재전송을 방지하기 위해 따로 관리 해 줄 필요성을 느낀다.

 

빌드를 하고 실행을 시키면, 파비콘이 탭영역에 표시되는 것을 확인 할 수 있다.

파비콘의 존재가 작고 초라하다

 

그리고 안좋은 요청에 대한 테스트도 확인 하였다.

400은 빨간색으로 표시하여 경각심을 불러일으킨다.

여기서 의아한 점은 500에러나 안좋은 응답을 받더라도 다시 연결을 시도하여 favicon.ico을 요청한다는 것이다.

그렇다

어떠한 경우라도 서버로부터 favicon.ico를 받아오는 것이 브라우저가 가장 지켜야할 duty인것이다.