如何实现一个通用的IHttpHandler 万能的IHttpHandler HttpWebRequest文件上传
Posted on 2013-08-02 21:56 冰天雪域 阅读(204) 评论(0) 编辑 收藏 举报昨天遇到一个比较奇怪的需求,大致是需要在服务器上部署一个http服务,但是服务的具体功能不知道,以后在客服端实现。这里介绍一下系统背景,有一个系统运(部署在美国)行了很多年了,给系统产生了很多文件,现在需要把该系统的文件(依据数据库中的记录)来做相应的archive,做了后发现里面还有一些独立的文件(不与数据库记录相关),那么这时我们需要删除这些独立的文件,或者把它们remove到其他地方,需要得到这些文件的list。后来想了想以后会不会还有别的什么需求啊,所以就想做一个通用的HTTPhandler了。这里说明一下:production时在美国,Archive在香港;在我们大陆的系统权限放的都比较开,在美国那个权限管的非常紧,我们是没有权限直接操作Production上的文件,所以才需要用http 协议来做。这里的http server部署到US,而client 却部署到hk。
整个解决方案如图:
其中
WebApp项目部署到Production上(us)
ConsoleApp部署到archive上(hk)
HttpRequestLibrary 是一个对象序列化的通用类以及一个请求类的包装,WebApp和ConsoleApp都需要引用该dll
ProcessAction是在客户端实现的,但是在服务器端反序列化是必须有该文件,所以该dll将会从client 上传到Production上。
首先我们来看看服务器端的实现:
首先需要创建一个ProcessActionHandler.ashx来处理客户端的调用:
public class ProcessActionHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; try { string inputstring = ReadInputStream(); if (!string.IsNullOrEmpty(inputstring)) { HttpRequestInfo requestinfo = inputstring; if (requestinfo.Process != null) { requestinfo.Process(requestinfo); } } else { //context.Response.StatusCode = 404; context.Response.Write("input error message"); } } catch (Exception ex) { context.Response.Write(ex.Message); } } private string ReadInputStream() { StringBuilder inputString = new StringBuilder(); using (Stream sr = HttpContext.Current.Request.InputStream) { byte[] data = new byte[1024 * 100]; int readCount = sr.Read(data, 0, data.Length); while (readCount > 0) { string text = Encoding.UTF8.GetString(data, 0, readCount); inputString.Append(text); readCount = sr.Read(data, 0, data.Length); } } return inputString.ToString(); } public bool IsReusable { get { return false; } } }
这里的HttpRequestInfo类是客户端创建的,这里调用HttpRequestInfo的Process方法也是客户端实现的。如何才能获得客户端的实现了,我们需要把客户端实现的dll文件上传到服务器上。
所以需要创建一个UploadActionHandler.ashx来上传客户端的处理:
public class UploadActionHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; string baseFilePath = context.Server.MapPath("Bin"); if (context.Request.Files.Count > 0) { try { HttpPostedFile file = context.Request.Files[0]; FileInfo fileInfo = new FileInfo(file.FileName); if (fileInfo.Extension.Equals(".dll")) { string tempPath = tempPath = Path.Combine(baseFilePath, fileInfo.Name); file.SaveAs(tempPath); context.Response.Write("Success"); } else { context.Response.Write("Failed:\r\n There only upload dll file"); } } catch (Exception ex) { context.Response.Write("Failed:\r\n" + ex.Message); } } else { context.Response.Write("Failed:\r\nThe Request has not upload file"); } } public bool IsReusable { get { return false; } } }
那么对象时如何序列化和反序列化,以及HttpRequestInfo的定义是什么样的了,这就要参考我们的HttpRequestLibrary项目了。
namespace HttpRequestLibrary { using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.Remoting.Messaging; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Soap; using System.Text; using System.Web; public enum FormatterType { /// <summary> /// SOAP消息格式编码 /// </summary> Soap, /// <summary> /// 二进制消息格式编码 /// </summary> Binary } public static class SerializationHelper { private const FormatterType DefaultFormatterType = FormatterType.Binary; /// <summary> /// 按照串行化的编码要求,生成对应的编码器。 /// </summary> /// <param name="formatterType"></param> /// <returns></returns> private static IRemotingFormatter GetFormatter(FormatterType formatterType) { switch (formatterType) { case FormatterType.Binary: return new BinaryFormatter(); case FormatterType.Soap: return new SoapFormatter(); } throw new NotSupportedException(); } /// <summary> /// 把对象序列化转换为字符串 /// </summary> /// <param name="graph">可串行化对象实例</param> /// <param name="formatterType">消息格式编码类型(Soap或Binary型)</param> /// <returns>串行化转化结果</returns> /// <remarks>调用BinaryFormatter或SoapFormatter的Serialize方法实现主要转换过程。 /// </remarks> public static string SerializeObjectToString(object graph, FormatterType formatterType) { using (MemoryStream memoryStream = new MemoryStream()) { IRemotingFormatter formatter = GetFormatter(formatterType); formatter.Serialize(memoryStream, graph); Byte[] arrGraph = memoryStream.ToArray(); return Convert.ToBase64String(arrGraph); } } public static string SerializeObjectToString(object graph) { return SerializeObjectToString(graph, DefaultFormatterType); } /// <summary> /// 把已序列化为字符串类型的对象反序列化为指定的类型 /// </summary> /// <param name="serializedGraph">已序列化为字符串类型的对象</param> /// <param name="formatterType">消息格式编码类型(Soap或Binary型)</param> /// <typeparam name="T">对象转换后的类型</typeparam> /// <returns>串行化转化结果</returns> /// <remarks>调用BinaryFormatter或SoapFormatter的Deserialize方法实现主要转换过程。 /// </remarks> public static T DeserializeStringToObject<T>(string graph, FormatterType formatterType) { Byte[] arrGraph = Convert.FromBase64String(graph); using (MemoryStream memoryStream = new MemoryStream(arrGraph)) { IRemotingFormatter formatter = GetFormatter(formatterType); return (T)formatter.Deserialize(memoryStream); } } public static T DeserializeStringToObject<T>(string graph) { return DeserializeStringToObject<T>(graph, DefaultFormatterType); } } [Serializable] public class HttpRequestInfo { public HttpRequestInfo() { ContentData = new byte[0]; CommData = new Dictionary<string, string>(); } public byte[] ContentData { set; get; } public Action<HttpRequestInfo> Process { set; get; } public Dictionary<string, string> CommData { set; get; } public override string ToString() { string graph = SerializationHelper.SerializeObjectToString(this); return graph; } public static implicit operator HttpRequestInfo(string contentString) { return SerializationHelper.DeserializeStringToObject<HttpRequestInfo>(contentString); } } }
那么客服端如何来操作服务器端了,需要查看ProcessAction项目的实现了:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.IO; using HttpRequestLibrary; using System.Web; namespace ProcessAction { public class HttpCommProcess { public static bool UploadFile(string address, string fileNamePath, out string error) { try { error = string.Empty; string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x"); byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + strBoundary + "\r\n"); StringBuilder sb = new StringBuilder(); sb.Append("--"); sb.Append(strBoundary); sb.Append("\r\n"); sb.Append("Content-Disposition: form-data; name=\""); sb.Append("file"); sb.Append("\"; filename=\""); sb.Append(fileNamePath); sb.Append("\""); sb.Append("\r\n"); sb.Append("Content-Type: "); sb.Append(@"application\octet-stream"); sb.Append("\r\n"); sb.Append("\r\n"); string strPostHeader = sb.ToString(); byte[] postHeaderBytes = Encoding.UTF8.GetBytes(strPostHeader); HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(address)); httpReq.Method = "POST"; httpReq.AllowWriteStreamBuffering = false; httpReq.Timeout = 300000; httpReq.ContentType = "multipart/form-data; boundary=" + strBoundary; string responseText = string.Empty; using (FileStream fs = new FileStream(fileNamePath, FileMode.Open, FileAccess.Read)) { BinaryReader r = new BinaryReader(fs); httpReq.ContentLength = fs.Length + postHeaderBytes.Length + boundaryBytes.Length; ; byte[] buffer = new byte[fs.Length]; int size = r.Read(buffer, 0, buffer.Length); using (Stream postStream = httpReq.GetRequestStream()) { postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length); postStream.Write(buffer, 0, size); postStream.Write(boundaryBytes, 0, boundaryBytes.Length); } } WebResponse webRespon = httpReq.GetResponse(); using (StreamReader s = new StreamReader(webRespon.GetResponseStream())) { responseText = s.ReadToEnd(); } if (responseText.Contains("Success")) { return true; } else { error = "UploadFile :" + responseText; return false; } } catch (Exception ex) { error = "UploadFile:" + ex.Message; return false; } } public static void SendHttpRequestData( string url,string reuestContent) { try { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); request.Method = "POST"; request.ContentType = "text/xml"; request.KeepAlive = false; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; using (Stream sr = request.GetRequestStream()) { byte[] data = Encoding.UTF8.GetBytes(reuestContent); sr.Write(data, 0, data.Length); } HttpWebResponse response = (HttpWebResponse)request.GetResponse(); if (response.StatusCode == HttpStatusCode.OK) { StringBuilder responseMessage = new StringBuilder(); using (Stream sr = response.GetResponseStream()) { byte[] data = new byte[1024 * 10]; int readcount = sr.Read(data, 0, data.Length); while (readcount > 0) { string str = Encoding.UTF8.GetString(data, 0, readcount); responseMessage.Append(str); readcount = sr.Read(data, 0, data.Length); } Console.WriteLine(responseMessage); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } public static string GetUploadFileContent(string filename) { HttpRequestInfo requestInfo = new HttpRequestInfo(); FileInfo file = new FileInfo(filename); requestInfo.CommData.Add("FileName", file.Name); requestInfo.ContentData = new byte[file.Length]; using (Stream sr = File.OpenRead(filename)) { sr.Read(requestInfo.ContentData, 0, requestInfo.ContentData.Length); } requestInfo.Process = (x) => { try { string tempfile = Path.Combine(@"c:\test", x.CommData["FileName"]); using (Stream wr = File.Open(tempfile, FileMode.OpenOrCreate, FileAccess.Write)) { wr.Write(x.ContentData, 0, x.ContentData.Length); } HttpContext.Current.Response.Write("Success"); } catch (Exception ex) { HttpContext.Current.Response.Write(ex.Message); } }; return requestInfo.ToString(); } public static string GetFileNames(string folderpath) { HttpRequestInfo requestInfo = new HttpRequestInfo(); requestInfo.CommData.Add("FolderPath", folderpath); requestInfo.Process = (x) => { try { DirectoryInfo dir=new DirectoryInfo( x.CommData["FolderPath"]); foreach (FileInfo item in dir.GetFiles()) { HttpContext.Current.Response.Write(item.FullName+Environment.NewLine); } HttpContext.Current.Response.Write("Success"); } catch (Exception ex) { HttpContext.Current.Response.Write(ex.Message); } }; return requestInfo.ToString(); } } }
这里我们来看看GetFileNames方法的实现吧:
public static string GetFileNames(string folderpath) { HttpRequestInfo requestInfo = new HttpRequestInfo(); requestInfo.CommData.Add("FolderPath", folderpath); requestInfo.Process = (x) => { try { DirectoryInfo dir=new DirectoryInfo( x.CommData["FolderPath"]); foreach (FileInfo item in dir.GetFiles()) { HttpContext.Current.Response.Write(item.FullName+Environment.NewLine); } HttpContext.Current.Response.Write("Success"); } catch (Exception ex) { HttpContext.Current.Response.Write(ex.Message); } }; return requestInfo.ToString(); } }
很显然这里的Process就是服务器端将要call的回调函数。那么这个处理很显然是在客户端,服务器端如何才能识别了,就需要把该代码上传到服务器端。
那么最终客服端该如何调用该代码了:
static void Main(string[] args) { string error = string.Empty; bool uploaded = HttpCommProcess.UploadFile("http://vihk2awwwdev01/webapp/UploadActionHandler.ashx", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessAction.dll"), out error); if (!uploaded) { Console.WriteLine(error); } else { ///upload file string content = HttpCommProcess.GetUploadFileContent(@"C:\IPC.LOG"); Console.WriteLine("Upload Fils"); HttpCommProcess.SendHttpRequestData("http://vihk2awwwdev01/webapp/ProcessActionHandler.ashx", content); //get file List content = HttpCommProcess.GetFileNames(@"C:\ArchiveInfoCenter\ArchiveInfoCenter"); Console.WriteLine("Get Fils List"); HttpCommProcess.SendHttpRequestData("http://vihk2awwwdev01/webapp/ProcessActionHandler.ashx", content); } Console.ReadLine(); }
首先上传dll文件,然后在发送http请求,运行结果如图:
客户端结果:
服务器文件上传结果(这里只能上传小文件,大文件序列化和反序列化会很慢很慢)
服务器上原文件目录:
在某种程度上我也不赞成这样做,会很危险的。这里只是纯粹从技术的角度来讲如何实现,有不好的地方还请大家拍砖。
源码下载地址:http://download.csdn.net/detail/dz45693/5856523