⚠WARNING
ASP.NET에 대한 포스팅이 아닙니다
나가실 문은 오른쪽 하단입니다
나가실 문은 오른쪽 하단입니다
포스팅 텀이 자꾸 길어지고 있다.
앞으로 더 진행해야 할 부분이 DB 연동과 서버 페이지 랜더링인데, 외부 패키지 없이 진행하려니.. 둘 다 쉽지가 않다.
그래서 중간에 텀 또는 시행착오가 없는 내용을 포스팅을 하기가 힘들 거 같다.
DB는 MS의 것을 쓰려고 해도 최소 ado.net을 설치해야 한다.
그러면 외부 패키지를 쓰지 않는다는 조건을 만족할 수 없다.
그렇다
이제 와서 약한척 안된다는 척 뒤로 무를 수는 없다.
그렇다
타인과의 약속은 못 지키더라도 자기 자신과의 약속은 지켜야 한다.
그렇다
나는 이제 나의 한계점에 도전해야 한다.
우선, 숨고르기로.
랜더링 엔진을 만들기에 앞서 지금까지 작성한 소스를 정리하기로 하였다.
전에 했던 Request이어 Response도 따로 클래스로 만들어 코드를 정리하였다.
using System.IO;
using System.Net.Sockets;
namespace dotweb;
public class Response
{
private Request req;
private Socket client;
#region stateCodeString
const string stateCodes = @"100 Continue
101 Switching Protocol
102 Processing (WebDAV)
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status
208 ALREADY REPORTED
226 IM Used (HTTP Delta encoding)
300 Multiple Choice
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
306 unused
307 Temporary Redirect
308 Permanent Redirect
400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Timeout
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Payload Too Large
414 URI Too Long
415 Unsupported Media Type
416 Requested Range Not Satisfiable
417 Expectation Failed
418 I'm a teapot
421 Misdirected Request
422 Unprocessable Entity (WebDAV)
423 Locked (WebDAV)
424 Failed Dependency (WebDAV)
426 Upgrade Required
428 Precondition Required
429 Too Many Requests
431 Request Header Fields Too Large
451 Unavailable For Legal Reasons
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
505 HTTP Version Not Supported
506 Variant Also Negotiates
507 Insufficient Storage
508 Loop Detected (WebDAV)
510 Not Extended
511 Network Authentication Required";
#endregion
readonly Dictionary<int, string> coDic = (from s in stateCodes.Split('\n')
select (num: int.Parse(s[0..3]), str:s)
).ToDictionary(s => s.num, s => s.str);
int sCode = 500; //Status code
string sCodeStr => coDic[sCode];
public bool sended { get; private set; }
public Response(Request req)
{
this.req = req;
this.client = req.client;
}
public Response status(int code)
{
sCode = code;
if(coDic.ContainsKey(code)) return this;
throw new ArgumentException("UNKNOW STATUS CODE");
}
private void ToClient(StringBuilder sb, byte[] body = null)
{
if (sCode <500 && req.sessionID == null)
{
sb.AddL("set-cookie: sessionID=" + req.newSessionID() + "; path=/; httponly");
}
sb.AddL();
client.Send(Encoding.UTF8.GetBytes(sb.ToString()));
if(body != null) client.Send(body);
this.sended = true;;
}
public void send(string msg, int statusCode = 200)
{
status(statusCode);
var header = new StringBuilder(100);
var bodyData = Encoding.UTF8.GetBytes(msg);
header.AddL("HTTP/1.1 " + sCodeStr);
header.AddL("date: " + Util.uTime);
header.AddL("Server: test server");
header.AddL("Content-type:text/plain; charset=UTF-8");
header.AddL("Content-Length: " + bodyData.Length);
header.AddL("Connection: close");
ToClient(header, bodyData);
}
public void sendReqFIle()
{
string publicPath = Path.Combine(Util.sPath, "public");
Ftag ftag = Util.getFInfo(publicPath + req.url);
FileInfo info = ftag.info;
if (!info.Exists || !info.FullName.StartsWith(publicPath))
{
send("페이지를 찾을 수 없습니다.", 404);
return;
}
status(ftag.etag == req.GetD("If-None-Match") ? 304 : 200);
var header = new StringBuilder(100);
header.AddL($"HTTP/1.1 " + sCodeStr);
header.AddL("Accept-Ranges: none");
header.AddL("Cache-Control: public, max-age=0");
header.AddL("Last-Modified: " + ftag.modiTime);
header.AddL("Etag: " + ftag.etag);
header.AddL("Date: " + Util.uTime);
if (sCode != 304)
{
header.AddL("Content-type: " + ftag.minetype);
header.AddL("Content-Length: " + info.Length);
}
header.AddL("Server: test server");
header.AddL("Connection: close");
ToClient(header, sCode != 304 ? File.ReadAllBytes(info.FullName) : null);
}
public void reDirect(string url)
{
var header = new StringBuilder(100);
header.AddL("HTTP/1.1 302 Found");
header.AddL("date: " + Util.uTime);
header.AddL("Server: test server");
header.AddL("location: " + url);
header.AddL("Content-Length: 0");
header.AddL("Connection: close");
ToClient(header);
}
}
그리고 엔트리 포인트에 있던 코드들도 클래스 파일로 옴겼다.
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace dotweb;
public class Express
{
Socket server = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Dictionary<string,Action<Request,Response>> getDic = new();
Dictionary<string,Action<Request,Response>> postDic = new();
public int? port {get; private set;} = null;
public void listen(int port, Action<int> callback = null)
{
this.port = port;
var ipep = new IPEndPoint(IPAddress.Any, port);
server.Bind(ipep);
server.Listen(100);
if(callback!=null) callback(port);
while (true)
{
Socket client = server.Accept();
Task.Run(() => newWork(client)); //자식 스레드에 인계
}
}
public void get(string url, Action<Request,Response> callback) => getDic.Add(url,callback);
public void post(string url, Action<Request,Response> callback) => postDic.Add(url,callback);
public void all(string url, Action<Request,Response> callback){
getDic.Add(url,callback);
postDic.Add(url,callback);
}
void newWork(Socket client)
{
client.ReceiveTimeout = 5000; //최대 5초 기다려줌을 선언
Request req = null;
Response res = null;
try
{
req = new Request(client);
res = new Response(req);
Func<bool> troubleCheck = (!client.Connected || req.Count is 0, req.err) switch
{
(true, _) => () => { WriteLine("Disconnected: by client"); return true; }
,
(_, null) => () => { WriteLine(req.First().Value); return false; }
,
(_, var _) => () => { res.send(req.err, 400); return true; }
};
if (troubleCheck()) return;
//WriteLine("hello~ " + (req.nickname ?? "newbie"));
if(req.method == Request.Method.GET) getDic.GetD(req.url)?.Invoke(req,res);
if(res.sended) return;
if(req.method == Request.Method.POST) postDic.GetD(req.url)?.Invoke(req,res);
if(res.sended) return;
res.sendReqFIle();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
WriteLine("Disconnected: timeout");
}
catch (Exception ex)
{
WriteLine(ex);
try
{
res.send("서버 에러!", 500);
}
catch (Exception ex2) { WriteLine(ex2); }
}
finally
{
//WriteLine("bye~ " + (req?.nickname ?? "anonymous"));
client.Close();
}
}
}
재법 정리가 된 느낌이다.
그리고 마지막 엔트리 포인트 부분
using System.Diagnostics;
using dotweb;
var app = new Express();
app.get("/", (req, res) => req.url = "/index.html");
app.all("/test/send", (req, res) => {
req.Sess["name"] = req.param.Get("name");
res.reDirect("/test/reply");
});
app.get("/test/reply", (req, res) => {
res.send(req.Sess.GetD("name", "Null") + "님 반갑습니다.");
});
app.post("/test/multipart", (req, res) => {
foreach (J tem in (req.jo["myfile"] as JL).getList())
{
byte[] bytes = tem["data"];
if (bytes?.Length is null or 0) continue;
string fanme = tem["filename"].Trim('"');
Util.saveFile("public/upload/" + fanme, bytes);
}
string fname = req.jo["fname"][0]["data"];
string lname = req.jo["lname"][0]["data"];
res.send(fname + " " + lname);
});
app.listen(80, port => {
System.Console.WriteLine($"Listening http://localhost:{port}");
if (Env.OSVersion.Platform == PlatformID.Win32NT)
{
Process.Start("explorer", "http://localhost");
}
});
찬찬히 살펴보고 있자면, 마치 node express의 그것을 보는 것 같은 착각마저 든다.
그렇다
무엇이든 한번 보게 되면 독창적으로 되기가 힘들다.
'C# > 근웹 연대기' 카테고리의 다른 글
c#으로 근본 없는 웹서버 개발기 18 : cshtml 구문 분석(이론) (0) | 2022.01.08 |
---|---|
c#으로 근본 없는 웹서버 개발기 17 : 서버페이지 렌더링 계획 (0) | 2022.01.07 |
c#으로 근본 없는 웹서버 개발기 15 : multipart/form-data 파일전송 (0) | 2021.12.27 |
c#으로 근본 없는 웹서버 개발기 14 : json parse 만들기 (0) | 2021.12.26 |
c#으로 근본 없는 웹서버 개발기 13 : json stringify 만들기 (0) | 2021.12.24 |