一個簡單的檔案伺服器實現方案
引子
最近公司的系統約來越多,基本上每個系統都需要用到“資源”,以前只是簡單的把“資源”放到Web伺服器中,但是這樣的話有一個頭痛的問題----如何去管理“資源”?
想法
現在不是很流行API嘛,大家好像都在整什麼面向服務、面向資源、RESTful什麼的,據說在與複雜性的鬥爭中,人們討論表象化狀態轉移(REST)已成為了一種時尚!我對這些概念也就是知道個大概,但是也不能解釋的很清楚,但是用意和優點還是很明確的!說白了就是各式各樣的“API”,可能我的理解有偏差,還望大家海涵,哈哈!
HTTP中有幾個常見謂詞,分別是GET/POST/PUT/DELETE,這也正是對應了我們經常說到的CRUD,含義就是對一個資源的增刪改查!
那咱能不能來一個檔案API呢?實現對一個一個檔案的CRUD?
說時遲,那時快
既然有了想法,咱就得開始幹了!那麼接下來的問題又來了,怎麼幹?
檔案的增刪改查很簡單,基本功唄!
資料格式?位元組陣列吧,不用轉來轉去,
資料傳輸呢?就跟一般的API一樣走HTTP協議,HTTP請求報文中分為兩個部分:請求頭和請求體,既然這樣,正好符合我們的需求,請求體承載檔案流的位元組陣列,請求頭中附加一些額外的資訊!
說到這,基本上大概的“形狀”就有了!那咱就開始幹!!!
新增一個Web應用程式作為服務端,WebForms或者Mvc的都可以。我這裡演示的是Mvc的!
不廢話,先上程式碼(只有上傳操作),待會大概解釋一下。
// *********************************************************************** // Project : Beimu.Bfs // Assembly : Beimu.Bfs.Web // Author : iceStone // Created : 2014年01月03日 10:23 // // Last Modified By : iceStone // Last Modified On : 2014年01月03日 10:23 // ***********************************************************************// <copyright file="DefaultController.cs" company="Wedn.Net"> // Copyright (c) Wedn.Net. All rights reserved. // </copyright> // <summary></summary> // *********************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Web; using System.Web.Mvc; using Beimu.Bfs.Web.Utilities; namespace Beimu.Bfs.Web.Controllers { /// <summary> /// 預設控制器. /// </summary> /// <remarks> /// 2014年01月03日 10:23 Created By iceStone /// </remarks> public class DefaultController : Controller { /// <summary> /// 身份驗證 /// </summary> /// <param name="filterContext"></param> protected override void OnActionExecuting(ActionExecutingContext filterContext) { var uid = Request.Headers["bfs-uid"];// Request["uid"]; var pwd = Request.Headers["bfs-pwd"];//Request["pwd"]; if (!(string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(pwd))) { var user = new Users(); if (!user.Exist(uid) || user[uid] != pwd) { filterContext.Result = Json(new { Status = 400, Message = "使用者名稱或密碼不正確" }); return; } } base.OnActionExecuting(filterContext); } /// <summary> /// 上傳操作. /// </summary> /// <remarks> /// 2014年01月03日 10:23 Created By iceStone /// </remarks> /// <returns>ActionResult.</returns> public ActionResult Upload() { #region 批量 //var files = Request.Files; //if (files == null || files.Count == 0) return Json(new { Status = 101, Message = "" }); //var dict = new Dictionary<int, string>(); //int index = -1; //foreach (HttpPostedFile file in files) //{ // index++; // if (file == null || file.ContentLength == 0) continue; // var ext = Path.GetExtension(file.FileName); // if (!Config.UploadAllowType.Contains(ext)) continue; // if (file.ContentLength >= (Config.UploadMaxSize * 1024 * 1024)) continue; // string root = AppDomain.CurrentDomain.BaseDirectory; // string path = string.Format("{0}/{1}/{2}/{3}", Config.UploadRoot, dir, DateTime.Now.ToString("yyyy"), DateTime.Now.ToString("MM")); // string filename = GetStreamMd5(file.InputStream) + ext;//Path.GetFileName(file.FileName); // file.SaveAs(Path.Combine(root, path, filename)); // dict.Add(index, Config.SiteUrl + path + filename); //} //return Json(dict); #endregion string ext = Request.Headers.AllKeys.Contains("bfs-ext") ? Request.Headers["bfs-ext"] : ".jpg"; var dir = Request.Headers.AllKeys.Contains("bfs-dir") ? Request.Headers["bfs-dir"] : Request.Headers["bfs-uid"]; //dir = string.IsNullOrEmpty(dir) ? "common" : dir; using (var stream = Request.InputStream) { //var files = Request.Files; if (stream.Length == 0) return SetHeaders(104, "上傳檔案為空"); if (stream.Length >= (Config.UploadMaxSize * 1024 * 1024)) return SetHeaders(101, "上傳檔案過大"); //string root = AppDomain.CurrentDomain.BaseDirectory; string path = string.Format("/{0}/{1}/{2}/{3}/", Config.UploadRoot, dir, DateTime.Now.ToString("yyyy"), DateTime.Now.ToString("MM")); string filename = GetStreamMd5(stream) + ext;//Path.GetFileName(file.FileName); string fullPath = Server.MapPath(path); if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath); //var buffer = new byte[stream.Length]; //stream.Read(buffer, 0, buffer.Length); //將流的內容讀到緩衝區 //using (var fs = new FileStream(fullPath + filename, FileMode.CreateNew, FileAccess.Write)) //{ // fs.Write(buffer, 0, buffer.Length); // fs.Flush(); // //fs.Close(); //} //using (var reader=new StreamReader(stream)) //{ // using (var writer = new StreamWriter(fullPath + filename, false)) // { // writer.Write(reader.ReadToEnd()); // writer.Flush(); // } //} using (var fs = new FileStream(fullPath + filename, FileMode.Create)) { byte[] bytes = new byte[stream.Length]; int numBytesRead = 0; int numBytesToRead = (int)stream.Length; stream.Position = 0; while (numBytesToRead > 0) { int n = stream.Read(bytes, numBytesRead, Math.Min(numBytesToRead, int.MaxValue)); if (n <= 0) break; fs.Write(bytes, numBytesRead, n); numBytesRead += n; numBytesToRead -= n; } fs.Close(); } return SetHeaders(100, Config.SiteUrl + path + filename); } //if (file == null || file.ContentLength == 0) return SetHeaders(103, "上傳檔案為空"); //var ext = Path.GetExtension(file.FileName); //if (!Config.UploadAllowType.Contains(ext)) return SetHeaders(102, "上傳非法檔案"); //if (file.ContentLength >= (Config.UploadMaxSize * 1024 * 1024)) return SetHeaders(101, "上傳檔案過大"); //string root = AppDomain.CurrentDomain.BaseDirectory; //string path = string.Format("{0}/{1}/{2}/{3}", Config.UploadRoot, dir, DateTime.Now.ToString("yyyy"), DateTime.Now.ToString("MM")); //string filename = GetStreamMd5(file.InputStream) + ext;//Path.GetFileName(file.FileName); //string fullPath = Path.Combine(root, path); //if (!Directory.Exists(fullPath)) // Directory.CreateDirectory(fullPath); //file.SaveAs(Path.Combine(root, path, filename)); //return SetHeaders(100, Config.SiteUrl + path + filename); } [NonAction] public ContentResult SetHeaders(int status, string resault) { Response.Headers.Add("bfs-status", status.ToString()); Response.Headers.Add("bfs-result", resault); return Content(string.Empty); } /// <summary> /// 獲取檔案的MD5值 /// </summary> /// <remarks> /// 2013年11月28日 19:24 Created By 汪磊 /// </remarks> /// <param name="stream">檔案流</param> /// <returns>該檔案的MD5值</returns> [NonAction] public String GetStreamMd5(Stream stream) { var oMd5Hasher = new MD5CryptoServiceProvider(); byte[] arrbytHashValue = oMd5Hasher.ComputeHash(stream); //由以連字元分隔的十六進位制對構成的String,其中每一對錶示value 中對應的元素;例如“F-2C-4A” string strHashData = BitConverter.ToString(arrbytHashValue); //替換- strHashData = strHashData.Replace("-", ""); string strResult = strHashData; return strResult; } } }
看完程式碼的同學估計已經發現,其實實現的過程也有不順利,其實剛開始就是沒有想好資料放在請求報文的什麼位置,最初嘗試了附件的形式,的確可以實現,但是出於某種原因(下面會說到)還是折騰成現在的樣子了!
大概理一下流程:
- 首先一個請求過來,校驗請求頭中身份資訊是否存在並且合法,很簡單!我就是讀了配置檔案中定義的使用者列表。可以做擴充套件;
- 獲取檔案型別(請求頭中的資訊)和檔案存放的目錄(如果不存在目錄引數,預設以當前請求的使用者名稱作為基本目錄);
- 判斷請求體中有沒有內容,沒有內容返回錯誤碼和錯誤訊息;
- 判斷上傳檔案格式是否為允許格式(有個問題“客戶端欺騙服務端”,因為檔案格式是客戶端在請求頭中註明的,但是回頭想想好像沒什麼大問題,如果客戶端偽裝拓展名,最終按照他偽裝的拓展名儲存,當然這裡大家可以辦法提出更好更安全的想法),不允許返回錯誤碼和錯誤訊息;
- 判斷檔案大小是否允許,不允許返回錯誤碼和錯誤訊息;
- 獲取檔案流的MD5值作為檔名(防止重複資源佔用空間,網盤就是這麼幹的,所以說所謂的多少T,所謂的秒傳,都是扯淡),
- 最後以/{指定目錄}/{年份}/{月份}/{檔名}{拓展名}儲存到伺服器!(如果有需要做成叢集的話,那就需要我們再做一些拓展了,以後可以詳細介紹,實際上就是弄個數據庫弄個表儲存一下每個伺服器的資源情況,以及資源的儲存位置)
- 最後返回給客戶端檔案的地址
這樣我們最簡單的檔案API就完成了,當然了它是很簡陋的,但是我更在乎的是思想上的問題,方向對不對!其實咱們做這一行最關鍵的就是這麼點事,技術好不好都是時間的事!
好像還少點啥
怎麼覺著好像還是少點東西呢?
一般的API還有什麼東西?-------客戶端吶!總不能讓使用者自己做吧,那樣的結果只會有各種各樣的情況產生。
那再來個客戶端唄!
好的!
還是上程式碼吧?
using System; using System.Configuration; using System.IO; using System.Net; namespace Beimu.Bfs.Lib { public class BfsUploader { public readonly string ApiDomain = ConfigurationManager.AppSettings["bfs_server"]; public string Directory { get; set; } public string UserId { get; set; } public string Password { get; set; } /// <summary> /// 建構函式 /// </summary> /// <param name="uid">使用者名稱</param> /// <param name="pwd">密碼</param> /// <param name="dir">操作目錄</param> public BfsUploader(string uid, string pwd, string dir = "common") { Directory = dir; UserId = uid; Password = pwd; } /// <summary> /// 寫檔案 /// </summary> /// <param name="filePath">檔案路徑</param> /// <param name="result">結果</param> /// <returns>是否成功</returns> public bool WriteFile(string filePath, out string result) { var ext = Path.GetExtension(filePath); using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { using (var reader = new BinaryReader(fs)) { byte[] postData = reader.ReadBytes((int)fs.Length); return Post("upload", ext, postData, out result); } } } /// <summary> /// 寫檔案 /// </summary> /// <param name="ext">拓展名</param> /// <param name="postData">請求體</param> /// <param name="result">結果</param> /// <returns>是否成功</returns> public bool WriteFile(string ext, byte[] postData, out string result) { return Post("upload",ext, postData, out result); } /// <summary> /// 傳送請求 /// </summary> /// <param name="url">請求地址</param> /// <param name="ext">拓展名</param> /// <param name="postData">請求體</param> /// <param name="result">結果</param> /// <returns>是否成功</returns> private bool Post(string url, string ext, byte[] postData, out string result) { var request = (HttpWebRequest)WebRequest.Create(ApiDomain + url); request.Method = "POST"; request.Headers.Add("bfs-uid", UserId); request.Headers.Add("bfs-pwd", Password); request.Headers.Add("bfs-dir", Directory); request.Headers.Add("bfs-ext", ext); if (postData != null) { request.ContentLength = postData.Length; request.KeepAlive = true; Stream dataStream = request.GetRequestStream(); dataStream.Write(postData, 0, postData.Length); dataStream.Close(); } try { var response = (HttpWebResponse)request.GetResponse(); var status = response.Headers["bfs-status"]; result = response.Headers["bfs-result"]; return response.StatusCode == HttpStatusCode.OK && status == "100"; } catch (Exception e) { throw e; } //string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); //byte[] boundarybytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n"); //byte[] endbytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n"); ////1.HttpWebRequest //HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); //request.ContentType = "multipart/form-data; boundary=" + boundary; //request.Method = "POST"; //request.KeepAlive = true; //request.Credentials = CredentialCache.DefaultCredentials; //using (Stream stream = request.GetRequestStream()) //{ // //1.1 key/value // string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; // if (data != null) // { // foreach (string key in data.Keys) // { // stream.Write(boundarybytes, 0, boundarybytes.Length); // string formitem = string.Format(formdataTemplate, key, data[key]); // byte[] formitembytes = encoding.GetBytes(formitem); // stream.Write(formitembytes, 0, formitembytes.Length); // } // } // //1.2 file // string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n"; // byte[] buffer = new byte[4096]; // int bytesRead = 0; // for (int i = 0; i < files.Length; i++) // { // stream.Write(boundarybytes, 0, boundarybytes.Length); // string header = string.Format(headerTemplate, "file" + i, Path.GetFileName(files[i])); // byte[] headerbytes = encoding.GetBytes(header); // stream.Write(headerbytes, 0, headerbytes.Length); // using (FileStream fileStream = new FileStream(files[i], FileMode.Open, FileAccess.Read)) // { // while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) // { // stream.Write(buffer, 0, bytesRead); // } // } // } // //1.3 form end // stream.Write(endbytes, 0, endbytes.Length); //} ////2.WebResponse //HttpWebResponse response = (HttpWebResponse)request.GetResponse(); //using (StreamReader stream = new StreamReader(response.GetResponseStream())) //{ // return stream.ReadToEnd(); //} } } }
程式碼實現也很簡單,就是封裝請求操作罷了!
沒什麼邏輯,今天就不分析了!
The End~
其實用單獨的檔案伺服器的好處有很多;
其中比較關鍵的兩點:
第一、促使瀏覽器並行下載多個請求、提高載入速度。
對於瀏覽器而言,同時對同域名下的請求是有限的,IE應該是五個左右,具體可以監控請求檢視,而單獨部署靜態資原始檔可以解決一點問題;
第二、cookie-free,暨避免不必要的請求頭。
稍微說一點:瀏覽器請求伺服器的時候會帶著該域名下的Cookie等請求頭的資訊,而這些資訊對於資原始檔似乎是沒有任何意義的,只是增加負擔。
順便提醒一下,外部連結也不要太多,會適得其反!對SEO有影響。據權威(YAHOO)研究表明,應該是5個左右!
相關推薦
一個簡單的檔案伺服器實現方案
引子 最近公司的系統約來越多,基本上每個系統都需要用到“資源”,以前只是簡單的把“資源”放到Web伺服器中,但是這樣的話有一個頭痛的問題----如何去管理“資源”? 想法 現在不是很流行API嘛,大家好像都在整什麼面向服務、面向資源、RESTful什麼的,據說在與複雜性的鬥爭中,人們討論表象化狀態轉移(
Jetty實戰之 嵌入式執行Jetty實現簡單檔案伺服器
本文連結:http://blog.csdn.net/kongxx/article/details/7224423對於嵌入式執行Jetty,可以通過簡單的一些程式碼實現一個簡單的檔案伺服器的功能,如下:package com.google.code.garbagecan.jett
golang實現簡單檔案伺服器
用golang做一個簡單的檔案伺服器,http包提供了很好的支援,由於時間緊促,只看了http包中自己需要的一小部分,建議大家如果需要還是去看官網的文件,搜尋引擎搜尋出來的前幾個方法不是很符合需求. 主要用到的方法是http包的FileServer 第一個Demo: pac
Windows 上靜態編譯 Libevent 2.0.10 並實現一個簡單 HTTP 伺服器
假設 Visual Studio 2005 的安裝路徑為“D:\Program Files\Microsoft Visual Studio 8\”,Libevent 2.0.10 解壓後的路徑為“D:\libevent-2.0.10-stable”。 編譯生成L
一個簡單web伺服器的java實現
一個簡單的web伺服器在不考慮其效能及健壯性的情況下,通常只需實現的功能包括伺服器的啟動,它用於監聽某一個埠,接收客戶端發來的請求,並將響應結果返回給客戶端。本文將介紹一個簡單web伺服器的實現原理,它本身只能處理某個目錄下的靜態資原始檔(文字、圖片等)。採用java
C語言實現一個簡單的伺服器
C/S結構流程圖 服務端 socket函式 為了執行網路I/O,一個程序必須做的第一件事情就是建立一個socket函式 /* family 表示協議族 AF_INET(IPv4協議)、AF_INET6(IPv6協議)、AF_L
JAVA編寫的一個簡單的Socket實現的HTTP響應伺服器進階版
1、首先建立ServerSocket監聽8000埠,等待瀏覽器的連線。 public class HttpServer { //WEB_ROOT該伺服器的根目錄,這個目錄可以自己定義,主要是伺服器響應的檔案所在目錄 public static final Strin
採用Java nio 實現的一個簡單的伺服器
伺服器程式碼: package server.nio; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream;
使用 Azure Blob Stoage 實現一個靜態檔案伺服器
## 什麼是Azure Blob Stoage Azure Blob Stoage 是微軟Azure的物件儲存服務。國內的雲一般叫OSS,是一種用來儲存非結構化資料的服務,比如音訊,視訊,圖片,文字等等。使用者可以通過http在全球任意地方訪問這些資源。這些資源可以公開訪問,也可以私有訪問。看到這些描述立馬就
簡單HTTP伺服器實現
我們這裡實現一個簡單的HTTP伺服器,無論瀏覽器向我們請求什麼資料,我們都返回一個hello world //實現最簡單的http服務端程式 //返回hello world //http是應用協議,在傳輸層使用的是tcp協議所
一個簡單的C++實現有理數類的例子
這次本來是老師佈置的一個作業,老師提前把main.cpp給了我們,要求我們在標頭檔案中定義並且實現一個有理數類,使得執行程式後輸出的結果跟他給的一樣。 main.cpp如下: #include <stdio.h> #include "Rational.h" int main() { Ra
nginx簡單檔案伺服器配置
該部落格只是工作筆記 嗯,比較簡單,配置如下: server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/n
【MATLAB】一個簡單的程式實現細胞計數
##實現帶UI介面的程式,對細胞計數 首先在建立一個圖形使用者介面,副檔名為.fIg的檔案,在MATlAB命令視窗啟動GUIDE,進行佈局,大致佈局為自己想要的介面,下圖是我自己做的介面(圖一)。可以選中屬性進行編輯 圖一圖二右鍵按鈕進入編輯器,即按鈕1的回撥函式,在函
git 上傳一個空檔案的解決方案,可使用命令列建立.gitkeep檔案
什麼是.gitkeep .gitkeep是一個約定俗成的檔名並沒有什麼特殊規則, 它就相當於一個佔位,0位元組。那它有什麼用呢?當你提交提交,目錄中有空資料夾的時候,git會自動忽略掉,而有時候你就
go搭建一個簡單web伺服器
Go語言裡面提供了一個完善的net/http包,通過http包可以很 方便的就搭建起來一個可以執行的web服務。同時使用這個包能很簡單地對web的路由,靜態檔案,模版,cookie等數 據進行設定和操
.net core 2.0學習筆記(A11):實戰簡單檔案伺服器
前言:本案例適合入門來看。 首先,新建一個ASP.NET Core專案,專案名稱:StaticFilesServer 選擇空的模板,如下圖: 使用NUGet命令新增Microsoft.AspNetCore.StaticFiles引用: Ins
簡單工廠模式---一個簡單計算器的實現
1.面向物件程式設計 所有的程式設計初學者都會有這樣的問題,就是碰到問題就直覺地用計算機能夠理解的邏輯來描述和表達待解決的問題及具體的求解過程。這其實就是用計算機的方式去思考,是一種面向過程的開發方式,比如計算器這個程式,先要求輸入兩個數和運算子號,然後根據運算子號判
使用poco庫搭建簡單http伺服器實現hello world
原始碼例子如下: #include "Poco/Net/HTTPServer.h" #include "Poco/Net/HTTPRequestHandler.h" #include "Poco/Net/HTTPRequestHandlerFactory.h" #incl
基於tcp的c/s模型的一個簡單的socket實現
程式設計壞境是linux或者mac下。我是在mac下寫的,不過也是通過gcc編譯的。 通訊的業務模型在之前部落格裡面講得比較清楚了,函式功能之前部落格也有。現在附上鍊接。 http://blog.csdn.net/zxcvbnm0014/article/details/
總結:一個簡單的MFC實現最小化托盤效果
先上說明,最後我會附上程式碼,我也是臨時新學的,請諸位指正 對於標頭檔案中的CxxxDlg類定義而言,需要新增以下幾個地方,見下圖 對於上圖中的最下面的public部分,略作解釋 NOTIFYICONDATA m_nid; //新增成員變數 afx_msg LRESUL