亚洲一卡二卡三卡四卡2021|国产凹凸在线观看一区二区|国产美女屁股直流白浆视频无遮挡|日本免费一二区在线电影|性激烈欧美三级在线播放|国产国产人免费观看在线视频

認證培訓,h3c認證體系,網(wǎng)絡(luò )工程師
豐沃創(chuàng  )新

HTTP服務(wù)器中遇到的一些問(wèn)題

  • 發(fā)布時(shí)間: 2018-4-27 9:06:27

前不久,手寫(xiě)了個(gè)服務(wù)器,并不難,還是基于 HttpListener ,敲簡(jiǎn)單!

當然還是基于最早寫(xiě)的一個(gè) Server 雛形,項目名為 Kserver,KServer 當初是為了當初自己想用 C# 實(shí)現 WebDav 的一些想法,后來(lái)也沒(méi)有繼續寫(xiě)下去,工程量太大了,有興趣的朋友可以看看 IETF RFC4918 中的協(xié)議定義嘗試實(shí)現一把,會(huì )很愉快的。

說(shuō)說(shuō)我的 Kserver 的調用,基本上三兩行代碼的事情。

  1. int port = 6600; 
  2. KServer kServer = new KServer(port); 
  3. kServer.OnRequest += KServer_OnRequest; 
  4. kServer.OnError += KServer_OnError; 
  5. kServer.Start(); 
  6. Console.WriteLine("listening on port {0} ...", port); 

在 KServer_OnRequest 中處理正常的 HTTP 請求,在 KServer_OnError 中處理程序錯誤,通常這里返回 HTTP 500 給客戶(hù)端。

說(shuō)一個(gè)坑爹的事情,這個(gè)程序啟動(dòng)后占用 6600 端口,然后在 Apache 配置了反向代理。

  1. <VirtualHost *:80> 
  2.     ServerName 1ll.co 
  3.     ProxyRequests off 
  4.     <Proxy *> 
  5.     Order deny,allow 
  6.     Allow from all 
  7.     </Proxy> 
  8.     ProxyPass / http://localhost:6600/ 
  9.     ProxyPassReverse / http://localhost:6600/ 
  10.     ProxyPassReverseCookieDomain http://localhost:6600 http://1ll.co 
  11.     ProxyPassReverseCookiePath / http://localhost:6600/ 
  12. </VirtualHost> 

但是寫(xiě) Cookie 始終不成功,寫(xiě) Cookie 的關(guān)鍵代碼如下:

  1. resp.AppendHeader("Set-Cookie"name + "=" + value + "; path=/; domain=" + host + "; expires=" + expireGMT); 

resp 是 KHttpServer.IHttpListenerResponse 的實(shí)現,繼承于 HttpListenerResponse,我設置 Host 為 req.Url.Host。這個(gè)在本機是不會(huì )有問(wèn)題的,單獨在服務(wù)器中使用 80 端口也不會(huì )有問(wèn)題,有問(wèn)題的是即便通過(guò)反向代理,獲取 Headers 中 的 Host 值始終還是 localhost,要通過(guò) X-Forwarded-Host 才可以,這個(gè)大學(xué)時(shí)好歹了解過(guò),平時(shí)開(kāi)發(fā)全部基于 IIS,沒(méi)有反向代理,頭一回遇到。

  1. var headers = obj.Request.Headers; 
  2. if (string.IsNullOrEmpty(_Host)) 
  3.     // 是否有反向代理 
  4.     bool poweredByProxy = false
  5.     IEnumerator keyenum = headers.GetEnumerator(); 
  6.     while (keyenum.MoveNext()) 
  7.     { 
  8.         string key = keyenum.Current.ToString(); 
  9.         if (key == "X-Forwarded-Host"
  10.         { 
  11.             _Host = headers[key]; 
  12.             poweredByProxy = true
  13.             break; 
  14.         } 
  15.     } 
  16.     // 沒(méi)有反向代理,就使用默認 Host 
  17.     if (!poweredByProxy) _Host = obj.Request.Url.Host; 

接下來(lái)就是模板引擎了,不用 Razor 了,說(shuō)真的對 Razor 漸漸的沒(méi)啥好感了,感覺(jué)挺笨重,所以選用了 DotLiquid,用 Liquid 做模板引擎的應用可以說(shuō)是非常多了。

  • DotLiquid http://dotliquidmarkup.org/

于是擴展了 String 類(lèi),增加了 Html 模板文件渲染 Html 的方法:

  1. public static string AsHtmlFromTemplate(this string tmpl, object model) 
  2.  { 
  3.      string html = Template.Parse(tmpl).Render(Hash.FromAnonymousObject(model)); 
  4.      return html; 
  5.  } 

然后包含模板頁(yè)渲染的寫(xiě)法就變成醬嬸了。

  1. string postListHtmlTmpl = ResourceHelper.LoadStringResource("postlist.html"); 
  2. string adminHtmlTmpl = ResourceHelper.LoadStringResource("admin.html"); 
  3. obj.Response.AsHtml(adminHtmlTmpl.AsHtmlFromTemplate(new 
  4.     RenderBody = postListHtmlTmpl.AsHtmlFromTemplate(new 
  5.     { 
  6.         PageData = pageData.ToArray(), 
  7.         NaviData = naviData, 
  8.         CurrentPage = page.ToString(), 
  9.         Error = error, 
  10.         Success = success 
  11.     }) 
  12. })); 

RenderBody 是模仿 Razor 搞的個(gè)關(guān)鍵字,表示是子頁(yè)顯示內容的區域。

對于字體、腳本(第三方)、圖片這些靜態(tài)資源,我的想法是既然不會(huì )有大的變動(dòng),就讓他永久緩存在瀏覽器好了。

  1. obj.Response.AppendHeader("Cache-Control""max-age=315360000"); 

其他的就是處理 POST ,處理 Cookie 了。HttpListenerRequest 是沒(méi)法獲取 Form 表單的值的,只能讀取 InputStream 中的值,然后自己根據鍵值對獲取了。Cookie 是不能簡(jiǎn)單的通過(guò)鍵值對分割,查詢(xún)值按照等號分割沒(méi)關(guān)系,因為 Value 都是編碼了的,不會(huì )含有等號,但是 Cookie 中是可能會(huì )有等號的,比如 Base64 編碼過(guò)的值里,大部分都有。

同樣,獲取 Cookie 的方法也木有,自己從 Header 里找吧,滑稽。

  1. public static string GetCookie(this KHttpServer.IHttpListenerRequest req, string name
  2.     System.Collections.Specialized.NameValueCollection headers = req.Headers; 
  3.     string cookies = headers["Cookie"]; 
  4.     if (cookies == null || cookies.Length < 1) return null
  5.     var dict = cookies.AsCookieParameters(); 
  6.     if (!dict.ContainsKey(name)) return null
  7.     return dict[name]; 

接下來(lái)模擬登陸成功后的跳轉,用過(guò) Asp.net 的知道有個(gè) Response.Redirect ,不過(guò) HttpListenerRequest 肯定是沒(méi)有這個(gè)方法的,可以通過(guò)設置 Header 302 重定向就行了,為啥是 302 不是 301,自己想吧。

  1. public static void Redirect(this KHttpServer.IHttpListenerResponse resp, string url) 
  2.     resp.StatusCode = 302; 
  3.     resp.AppendHeader("Location", url); 
  4.     resp.Close(); 

對于較大的頁(yè)面,也許還是希望用 Gzip 壓縮一下,需要設置 Content-Encoding 為 Gzip。

  1. resp.AppendHeader("Content-Encoding""gzip"); 

我這里處理比較簡(jiǎn)單,是不管客戶(hù)端的 Accept-Type 的,不過(guò)現代瀏覽器基本都支持了。

對相應內容進(jìn)行壓縮:

  1. resp.AppendHeader("Content-Encoding""gzip"); 
  2. byte[] data = GzipCompressor.Compress(text); 
  3. MemoryStream ms = new MemoryStream(data); 
  4. AsStream(resp, ms, mime); 
  5. ms.Close(); 

既然是純 C#,沒(méi)有了 WebForm 和 MVC 這類(lèi)框架,分頁(yè)處理也顯得不簡(jiǎn)單了,從網(wǎng)上改造了一個(gè) PHP 寫(xiě)的分頁(yè)類(lèi),果然 PHP 是最好的語(yǔ)言!鷂→

這不是取數據時(shí)的分頁(yè),而是顯示時(shí)候的分頁(yè)。

  1. /// <summary> 
  2. /// 分頁(yè)處理類(lèi) 
  3. /// </summary> 
  4. public class PageNumber 
  5.     /// <summary> 
  6.     /// 是否顯示[首頁(yè)] 
  7.     /// </summary> 
  8.     public bool ShowFirstPage { get; set; } 
  9.  
  10.     /// <summary> 
  11.     /// 是否顯示[末頁(yè)] 
  12.     /// </summary> 
  13.     public bool ShowEndPage { get; set; } 
  14.  
  15.     /// <summary> 
  16.     /// 翻頁(yè)Url前綴 
  17.     /// </summary> 
  18.     public string UrlPrefix { get; set; } 
  19.  
  20.     public PageNumber() 
  21.     { 
  22.         ShowFirstPage = true
  23.         ShowEndPage = true
  24.         UrlPrefix = ""
  25.     } 
  26.  
  27.     /// <summary> 
  28.     /// 獲取分頁(yè),返回數據,如[["1","首頁(yè)","/page/1"]] 
  29.     /// </summary> 
  30.     /// <param name="page">當前頁(yè)</param> 
  31.     /// <param name="pages">總頁(yè)數</param> 
  32.     /// <returns></returns
  33.     public List<string[]> GetPageNumbers(int page, int pages) 
  34.     { 
  35.  
  36.         List<string[]> plists = new List<string[]>(); 
  37.  
  38.         //最多顯示多少個(gè)頁(yè)碼   
  39.         int _pageNum = 5; 
  40.         //當前頁(yè)面小于1 則為1   
  41.         page = page < 1 ? 1 : page; 
  42.         //當前頁(yè)大于總頁(yè)數 則為總頁(yè)數   
  43.         page = page > pages ? pages : page; 
  44.         //頁(yè)數小當前頁(yè) 則為當前頁(yè)   
  45.         pages = pages < page ? page : pages; 
  46.  
  47.         //計算開(kāi)始頁(yè)   
  48.         int _start = page - (int)Math.Floor((double)_pageNum / 2); 
  49.         _start = _start < 1 ? 1 : _start; 
  50.         //計算結束頁(yè)   
  51.         int _end = page + (int)Math.Floor((double)_pageNum / 2); 
  52.         _end = _end > pages ? pages : _end; 
  53.  
  54.         //當前顯示的頁(yè)碼個(gè)數不夠最大頁(yè)碼數,在進(jìn)行左右調整   
  55.         int _curPageNum = _end - _start + 1; 
  56.         //左調整   
  57.         if (_curPageNum < _pageNum && _start > 1) 
  58.         { 
  59.             _start = _start - (_pageNum - _curPageNum); 
  60.             _start = _start < 1 ? 1 : _start; 
  61.             _curPageNum = _end - _start + 1; 
  62.         } 
  63.         //右邊調整   
  64.         if (_curPageNum < _pageNum && _end < pages) 
  65.         { 
  66.             _end = _end + (_pageNum - _curPageNum); 
  67.             _end = _end > pages ? pages : _end; 
  68.         } 
  69.  
  70.         if (ShowFirstPage) 
  71.             plists.Add(new string[] { """首頁(yè)", string.IsNullOrEmpty(UrlPrefix) ? "" : UrlPrefix + "1" }); 
  72.  
  73.         if (page > 1) 
  74.         { 
  75.             plists.Add(new string[] { (page - 1).ToString(), "上頁(yè)", string.IsNullOrEmpty(UrlPrefix) ? "" : UrlPrefix + (page - 1).ToString() }); 
  76.         } 
  77.         for (int i = _start; i <= _end; i++) 
  78.         { 
  79.             plists.Add(new string[] { i.ToString(), i.ToString(), string.IsNullOrEmpty(UrlPrefix) ? "" : UrlPrefix + i.ToString() }); 
  80.         } 
  81.         if (page < _end) 
  82.         { 
  83.             plists.Add(new string[] { (page + 1).ToString(), "下頁(yè)" , string.IsNullOrEmpty(UrlPrefix) ? "" : UrlPrefix + (page + 1).ToString() }); 
  84.         } 
  85.  
  86.         if (ShowEndPage) 
  87.             plists.Add(new string[] { """末頁(yè)", string.IsNullOrEmpty(UrlPrefix) ? "" : UrlPrefix + (pages).ToString() }); 
  88.  
  89.         return plists; 
  90.     } 

用 SimpleMDE 作為 Markdown 編輯器,,誰(shuí)用誰(shuí)知道,對于富文本的排版,我始終無(wú)能為力,Word 也不會(huì )用,markdown 真好用!

  • SimpleMDE https://simplemde.com/

效果如下圖:

SimpleMDE 是沒(méi)有上傳圖片的功能,需要自己處理,不過(guò)自定義按鈕官方文檔中有,我只是做了寫(xiě)微小的工作,為按鈕加個(gè)選圖片和上傳的事件,這需要 jQuery 和 jQuery.Form 的支持。

  1. function upload(){ 
  2.     var sid = 'hTyx6Tm9Ikl06Ap'
  3.     var forms = $('#form_' + sid).length; 
  4.     if (forms > 0) { 
  5.         $('#form_' + sid).remove(); 
  6.     } 
  7.     var fhtml = '<form action="圖片上傳接口" method="post" enctype="multipart/form-data" style="display:none;" id="form_' + sid + '">'
  8.     fhtml += '<input id="input_' + sid + '" type="file" name="file">'
  9.     fhtml += '<input type="submit" value="upload" />'
  10.     fhtml += '</form>'
  11.     $('body').append(fhtml); 
  12.     $('#input_' + sid).change(function () { 
  13.         $('#form_' + sid).ajaxSubmit({ 
  14.             success: function (data) { 
  15.             alert(data); 
  16.             } 
  17.         }); 
  18.     }).click(); 

如果你的接口是外部服務(wù)或者阿里云OSS,要記得設置跨域,不然報錯,這個(gè)搞過(guò)開(kāi)發(fā)的都懂得。

最初版本的后臺 Markdown 渲染用的 Github 上的 star 最多的那一個(gè) Markdig,在 CentOS 7 下 mono 環(huán)境運行報錯,換了 CommonMark 使用,這個(gè)在 Nuget 上能找到。

最終的最終,把所有資源都打包進(jìn)了資源文件,用 ILMerge 合并程序集,你的服務(wù)端就只剩下一個(gè) EXE 了,滑稽 →_→

亚洲一卡二卡三卡四卡2021|国产凹凸在线观看一区二区|国产美女屁股直流白浆视频无遮挡|日本免费一二区在线电影|性激烈欧美三级在线播放|国产国产人免费观看在线视频