分享
 
 
 

在WinForm中通过HTTP协议向服务器端上传文件

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

相信用ASP.NET写一个上传文件的网页,大家都会写,但是有没有人想过通过在WinForm中通过HTTP协议上传文件呢?

有些人说要向服务器端上传文件,用FTP协议不是很简单吗?效率又高,为什么还要使用HTTP协议那么麻烦呢?这里面有几个原因:

(1)FTP服务器的部署相对麻烦,还要设置权限,权限设置不对,还会惹来一系列的安全问题。

(2)如果双方都还有防火墙,又不想开发FTP相关的一些端口时,HTTP就会大派用场,就像WEB Services能穿透防火墙一样。

(3)其他的...,还在想呢...

但是使用HTTP也有他的一些问题,例如不能断点续传,大文件上传很难,速度很慢,所以HTTP协议上传的文件大小不应该太大。

说了这么多,原归正传,一般来说,在Winform里通过HTTP上传文件有几种可选的方法:

(1)前面提到的Web Services,就是一种很好的方法,通过编写一个WebMethod,包含有 byte[] 类型的参数,然后调用Web Services的方法,文件内容就会以Base64编码传到服务器上,然后重新保存即可。

[WebMethod]

public void UploadFile(byte[] content,string filename){

Stream sw = new StreamWriter(...);

sw.Close();

}

当然,这种通过Base64编码的方法效率比较低,那么可以采用WSE,支持附件,并以2进制形式传送,效率会更高。

(2)除了通过WebService,另外一种更简单的方法就是通过WebClient或者HttpWebRequest来模拟HTTP的POST动作来实现。这时候首先需要编写一个asp.net web form来响应上传,代码如下:

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="UploadFileWeb.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<html>

<head>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">

<meta name="CODE_LANGUAGE" Content="C#">

<meta name="vs_defaultClientScript" content="JavaScript">

<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">

</head>

<body>

<form id="Form1" method="post" runat="server">

</form>

</body>

</html>

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.SessionState;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

namespace UploadFileWeb

{

/// <summary>

/// WebForm1 的摘要说明。

/// </summary>

public class WebForm1 : System.Web.UI.Page

{

private void Page_Load(object sender, System.EventArgs e)

{

// 在此处放置用户代码以初始化页面

foreach( string f in Request.Files.AllKeys)

{

HttpPostedFile file = Request.Files[f];

file.SaveAs(@"D:\Temp\" + file.FileName);

}

if( Request.Params["testKey"] != null )

{

Response.Write(Request.Params["testKey"]);

}

}

#region Web 窗体设计器生成的代码

override protected void OnInit(EventArgs e)

{

//

// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。

//

InitializeComponent();

base.OnInit(e);

}

/// <summary>

/// 设计器支持所需的方法 - 不要使用代码编辑器修改

/// 此方法的内容。

/// </summary>

private void InitializeComponent()

{

this.Load += new System.EventHandler(this.Page_Load);

}

#endregion

}

}

其实这个页面跟我们平常写的asp.net上传文件代码是一样的,在Web 页的Request对象中包含有Files这个对象,里面就包含了通过POST方式上传的所有文件的信息,这时所需要做的就是调用 Request.Files[i].SaveAs方法。

但是怎么让才能在WinForm里面模拟想Web Form POST 数据呢?System.Net命名空间里面提供了两个非常有用的类,一个是WebClient,另外一个是HttpWebRequest类。如果我们不需要通过代理服务器来上传文件,那么非常简单,只需要简单的调用WebClient.UploadFile方法就能实现上传文件:

private void button1_Click(object sender, System.EventArgs e)

{

WebClient myWebClient = new WebClient();

myWebClient.UploadFile("http://localhost/UploadFileWeb/WebForm1.aspx","POST",@"D:TempJavaJavaStartJavaStart2.exe");

}

是不是觉得很简单呢?确实就这么简单。

但是如果要通过代理服务器上传又怎么办呢?那就需要使用到HttpWebRequest,但是该类没有Upload方法,但是幸运的是我们通过Reflector反编译了WebClient.UploadFile方法后,我们发现其内部也是通过WebRequest来实现的,代码如下:

public byte[] UploadFile(string address, string method, string fileName)

{

string text1;

string text2;

WebRequest request1;

string text3;

byte[] buffer1;

byte[] buffer2;

long num1;

byte[] buffer3;

int num2;

WebResponse response1;

byte[] buffer4;

DateTime time1;

long num3;

string[] textArray1;

FileStream stream1 = null;

try

{

fileName = Path.GetFullPath(fileName);

time1 = DateTime.Now;

num3 = time1.Ticks;

text1 = "---------------------" + num3.ToString("x");

if (this.m_headers == null)

{

this.m_headers = new WebHeaderCollection();

}

text2 = this.m_headers["Content-Type"];

if (text2 != null)

{

if (text2.ToLower(CultureInfo.InvariantCulture).StartsWith("multipart/"))

{

throw new WebException(SR.GetString("net_webclient_Multipart"));

}

}

else

{

text2 = "application/octet-stream";

}

this.m_headers["Content-Type"] = "multipart/form-data; boundary=" + text1;

this.m_responseHeaders = null;

stream1 = new FileStream(fileName, FileMode.Open, FileAccess.Read);

request1 = WebRequest.Create(this.GetUri(address));

request1.Credentials = this.Credentials;

this.CopyHeadersTo(request1);

request1.Method = method;

textArray1 = new string[7];

textArray1[0] = "--";

textArray1[1] = text1;

textArray1[2] = "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"";

textArray1[3] = Path.GetFileName(fileName);

textArray1[4] = "\"\r\nContent-Type: ";

textArray1[5] = text2;

textArray1[6] = "\r\n\r\n";

text3 = string.Concat(textArray1);

buffer1 = Encoding.UTF8.GetBytes(text3);

buffer2 = Encoding.ASCII.GetBytes("\r\n--" + text1 + "\r\n");

num1 = 9223372036854775807;

try

{

num1 = stream1.Length;

request1.ContentLength = ((num1 + ((long) buffer1.Length)) + ((long) buffer2.Length));

}

catch

{

}

buffer3 = new byte[Math.Min(((int) 8192), ((int) num1))];

using (Stream stream2 = request1.GetRequestStream())

{

stream2.Write(buffer1, 0, buffer1.Length);

do

{

num2 = stream1.Read(buffer3, 0, buffer3.Length);

if (num2 != 0)

{

stream2.Write(buffer3, 0, num2);

}

}

while ((num2 != 0));

stream2.Write(buffer2, 0, buffer2.Length);

}

stream1.Close();

stream1 = null;

response1 = request1.GetResponse();

this.m_responseHeaders = response1.Headers;

return this.ResponseAsBytes(response1);

}

catch (Exception exception1)

{

if (stream1 != null)

{

stream1.Close();

stream1 = null;

}

if ((exception1 is WebException) || (exception1 is SecurityException))

{

throw;

}

throw new WebException(SR.GetString("net_webclient"), exception1);

}

return buffer4;

}

在这段代码里面其实最关键的就是如何模拟POST请求,通过分析代码和监视HTTP,我们可以发现模拟的POST格式如下:

-----------------------8c64f47716481f0 //时间戳

Content-Disposition: form-data; name="file"; filename="a.txt" //文件名

Content-Type: application/octet-stream

//文件的内容

-----------------------8c64f47716481f0

这时候,我们只需自己编码来模拟这么一组数据就行(我们还可以好好借鉴MS的代码呢),以下就是代码(声明一下,我是借用了别人的代码)

public class wwHttp

{

/// <summary>

/// Fires progress events when using GetUrlEvents() to retrieve a URL.

/// </summary>

public event OnReceiveDataHandler OnReceiveData;

/// <summary>

/// Determines how data is POSTed when cPostBuffer is set.

/// 1 - UrlEncoded

/// 2 - Multi-Part form vars

/// 4 - XML (raw buffer content type: text/xml)

/// </summary>

public int PostMode

{

get { return this.nPostMode; }

set { this.nPostMode = value; }

}

/// <summary>

/// User name used for Authentication.

/// To use the currently logged in user when accessing an NTLM resource you can use "AUTOLOGIN".

/// </summary>

public string Username

{

get { return this.cUsername; }

set { cUsername = value; }

}

/// <summary>

/// Password for Authentication.

/// </summary>

public string Password

{

get {return this.cPassword;}

set {this.cPassword = value;}

}

/// <summary>

/// Address of the Proxy Server to be used.

/// Use optional DEFAULTPROXY value to specify that you want to IE's Proxy Settings

/// </summary>

public string ProxyAddress

{

get {return this.cProxyAddress;}

set {this.cProxyAddress = value;}

}

/// <summary>

/// Semicolon separated Address list of the servers the proxy is not used for.

/// </summary>

public string ProxyBypass

{

get {return this.cProxyBypass;}

set {this.cProxyBypass = value;}

}

/// <summary>

/// Username for a password validating Proxy. Only used if the proxy info is set.

/// </summary>

public string ProxyUsername

{

get {return this.cProxyUsername;}

set {this.cProxyUsername = value;}

}

/// <summary>

/// Password for a password validating Proxy. Only used if the proxy info is set.

/// </summary>

public string ProxyPassword

{

get {return this.cProxyPassword;}

set {this.cProxyPassword = value;}

}

/// <summary>

/// Timeout for the Web request in seconds. Times out on connection, read and send operations.

/// Default is 30 seconds.

/// </summary>

public int Timeout

{

get {return this.nConnectTimeout; }

set {this.nConnectTimeout = value; }

}

/// <summary>

/// Error Message if the Error Flag is set or an error value is returned from a method.

/// </summary>

public string ErrorMsg

{

get { return this.cErrorMsg; }

set { this.cErrorMsg = value; }

}

/// <summary>

/// Error flag if an error occurred.

/// </summary>

public bool Error

{

get { return this.bError; }

set { this.bError = value; }

}

/// <summary>

/// Determines whether errors cause exceptions to be thrown. By default errors

/// are handled in the class and the Error property is set for error conditions.

/// (not implemented at this time).

/// </summary>

public bool ThrowExceptions

{

get { return bThrowExceptions; }

set { this.bThrowExceptions = value;}

}

/// <summary>

/// If set to a non-zero value will automatically track cookies. The number assigned is the cookie count.

/// </summary>

public bool HandleCookies

{

get { return this.bHandleCookies; }

set { this.bHandleCookies = value; }

}

public CookieCollection Cookies {

get { return this.oCookies; }

set { this.Cookies = value; }

}

public HttpWebResponse WebResponse {

get { return this.oWebResponse;}

set { this.oWebResponse = value; }

}

public HttpWebRequest WebRequest {

get { return this.oWebRequest; }

set { this.oWebRequest = value; }

}

// *** member properties

//string cPostBuffer = "";

MemoryStream oPostStream;

BinaryWriter oPostData;

int nPostMode = 1;

int nConnectTimeout = 30;

string cUserAgent = "West Wind HTTP .NET";

string cUsername = "";

string cPassword = "";

string cProxyAddress = "";

string cProxyBypass = "";

string cProxyUsername = "";

string cProxyPassword = "";

bool bThrowExceptions = false;

bool bHandleCookies = false;

string cErrorMsg = "";

bool bError = false;

HttpWebResponse oWebResponse;

HttpWebRequest oWebRequest;

CookieCollection oCookies;

string cMultiPartBoundary = "-----------------------------7cf2a327f01ae";

public void wwHTTP()

{

//

// TODO: Add constructor logic here

//

}

/// <summary>

/// Adds POST form variables to the request buffer.

/// HttpPostMode determines how parms are handled.

/// 1 - UrlEncoded Form Variables. Uses key and value pairs (ie. "Name","Rick") to create URLEncoded content

/// 2 - Multi-Part Forms - not supported

/// 4 - XML block - Post a single XML block. Pass in as Key (1st Parm)

/// other - raw content buffer. Just assign to Key.

/// </summary>

/// <param name="Key">Key value or raw buffer depending on post type</param>

/// <param name="Value">Value to store. Used only in key/value pair modes</param>

public void AddPostKey(string Key, byte[] Value)

{

if (this.oPostData == null)

{

this.oPostStream = new MemoryStream();

this.oPostData = new BinaryWriter(this.oPostStream);

}

if (Key == "RESET")

{

this.oPostStream = new MemoryStream();

this.oPostData = new BinaryWriter(this.oPostStream);

}

switch(this.nPostMode)

{

case 1:

this.oPostData.Write(Encoding.GetEncoding(1252).GetBytes(

Key + "=" + System.Web.HttpUtility.UrlEncode(Value) + "&"));

break;

case 2:

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes(

"--" + this.cMultiPartBoundary + "\r\n" +

"Content-Disposition: form-data; name=\"" +Key+"\"\r\n\r\n") );

this.oPostData.Write( Value );

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes("\r\n") );

break;

default:

this.oPostData.Write( Value );

break;

}

}

public void AddPostKey(string Key, string Value)

{

this.AddPostKey(Key,Encoding.GetEncoding(1252).GetBytes(Value));

}

/// <summary>

/// Adds a fully self contained POST buffer to the request.

/// Works for XML or previously encoded content.

/// </summary>

/// <param name="PostBuffer"></param>

public void AddPostKey(string FullPostBuffer)

{

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes(FullPostBuffer) );

}

public bool AddPostFile(string Key,string FileName)

{

byte[] lcFile;

if (this.nPostMode != 2) {

this.cErrorMsg = "File upload allowed only with Multi-part forms";

this.bError = true;

return false;

}

try

{

FileStream loFile = new FileStream(FileName,System.IO.FileMode.Open,System.IO.FileAccess.Read);

lcFile = new byte[loFile.Length];

loFile.Read(lcFile,0,(int) loFile.Length);

loFile.Close();

}

catch(Exception e)

{

this.cErrorMsg = e.Message;

this.bError = true;

return false;

}

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes(

"--" + this.cMultiPartBoundary + "\r\n" +

"Content-Disposition: form-data; name=\"" + Key + "\" filename=\"" +

new FileInfo(FileName).Name + "\"\r\n\r\n") );

this.oPostData.Write( lcFile );

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes("\r\n")) ;

return true;

}

/// <summary>

/// Return a the result from an HTTP Url into a StreamReader.

/// Client code should call Close() on the returned object when done reading.

/// </summary>

/// <param name="Url">Url to retrieve.</param>

/// <param name="WebRequest">An HttpWebRequest object that can be passed in with properties preset.</param>

/// <returns></returns>

protected StreamReader GetUrlStream(string Url,HttpWebRequest Request)

{

try

{

this.bError = false;

this.cErrorMsg = "";

if (Request == null)

{

Request = (HttpWebRequest) System.Net.WebRequest.Create(Url);

}

Request.UserAgent = this.cUserAgent;

Request.Timeout = this.nConnectTimeout * 1000;

// *** Save for external access

this.oWebRequest = Request;

// *** Handle Security for the request

if (this.cUsername.Length > 0)

{

if (this.cUsername=="AUTOLOGIN")

Request.Credentials = CredentialCache.DefaultCredentials;

else

Request.Credentials = new NetworkCredential(this.cUsername,this.cPassword);

}

// *** Handle Proxy Server configuration

if (this.cProxyAddress.Length > 0)

{

if (this.cProxyAddress == "DEFAULTPROXY")

{

Request.Proxy = new WebProxy();

Request.Proxy = WebProxy.GetDefaultProxy();

}

else

{

WebProxy loProxy = new WebProxy(this.cProxyAddress,true);

if (this.cProxyBypass.Length > 0)

{

loProxy.BypassList = this.cProxyBypass.Split(';');

}

if (this.cProxyUsername.Length > 0)

loProxy.Credentials = new NetworkCredential(this.cProxyUsername,this.cProxyPassword);

Request.Proxy = loProxy;

}

}

// *** Handle cookies - automatically re-assign

if (this.bHandleCookies)

{

Request.CookieContainer = new CookieContainer();

if (this.oCookies != null && this.oCookies.Count > 0)

{

Request.CookieContainer.Add(this.oCookies);

}

}

// *** Deal with the POST buffer if any

if (this.oPostData != null)

{

Request.Method = "POST";

switch (this.nPostMode)

{

case 1:

Request.ContentType = "application/x-www-form-urlencoded";

// strip off any trailing & which can cause problems with some

// http servers

// if (this.cPostBuffer.EndsWith("&"))

// this.cPostBuffer = this.cPostBuffer.Substring(0,this.cPostBuffer.Length-1);

break;

case 2:

Request.ContentType = "multipart/form-data; boundary=" + this.cMultiPartBoundary;

this.oPostData.Write( Encoding.GetEncoding(1252).GetBytes( "--" + this.cMultiPartBoundary + "\r\n" ) );

break;

case 4:

Request.ContentType = "text/xml";

break;

default:

goto case 1;

}

Stream loPostData = Request.GetRequestStream();

//loPostData.Write(lcPostData,0,lcPostData.Length);

this.oPostStream.WriteTo(loPostData);

byte[] buffer = new byte[this.oPostStream.Length];

buffer = this.oPostStream.ToArray();

Console.Write(Encoding.GetEncoding(1252).GetString(buffer,0,buffer.Length));

//*** Close the memory stream

this.oPostStream.Close();

this.oPostStream = null;

//*** Close the Binary Writer

this.oPostData.Close();

this.oPostData = null;

//*** Close Request Stream

loPostData.Close();

// *** clear the POST buffer

//this.cPostBuffer = "";

}

// *** Retrieve the response headers

HttpWebResponse Response = (HttpWebResponse) Request.GetResponse();

// ** Save cookies the server sends

if (this.bHandleCookies)

{

if (Response.Cookies.Count > 0)

{

if (this.oCookies == null)

{

this.oCookies = Response.Cookies;

}

else

{

// ** If we already have cookies update the list

foreach (Cookie oRespCookie in Response.Cookies)

{

bool bMatch = false;

foreach(Cookie oReqCookie in this.oCookies)

{

if (oReqCookie.Name == oRespCookie.Name)

{

oReqCookie.Value = oRespCookie.Name;

bMatch = true;

break; //

}

} // for each ReqCookies

if (!bMatch)

this.oCookies.Add(oRespCookie);

} // for each Response.Cookies

} // this.Cookies == null

} // if Response.Cookie.Count > 0

} // if this.bHandleCookies = 0

// *** Save the response object for external access

this.oWebResponse = Response;

Encoding enc;

try

{

if (Response.ContentEncoding.Length > 0)

enc = Encoding.GetEncoding(Response.ContentEncoding);

else

enc = Encoding.GetEncoding(1252);

}

catch

{

// *** Invalid encoding passed

enc = Encoding.GetEncoding(1252);

}

// *** drag to a stream

StreamReader strResponse =

new StreamReader(Response.GetResponseStream(),enc);

return strResponse;

}

catch(Exception e)

{

if (this.bThrowExceptions)

throw e;

this.cErrorMsg = e.Message;

this.bError = true;

return null;

}

}

/// <summary>

/// Return a the result from an HTTP Url into a StreamReader.

/// Client code should call Close() on the returned object when done reading.

/// </summary>

/// <param name="Url">Url to retrieve.</param>

/// <returns></returns>

public StreamReader GetUrlStream(string Url)

{

HttpWebRequest oHttpWebRequest = null;

return this.GetUrlStream(Url,oHttpWebRequest);

}

/// <summary>

/// Return a the result from an HTTP Url into a StreamReader.

/// Client code should call Close() on the returned object when done reading.

/// </summary>

/// <param name="Request">A Request object</param>

/// <returns></returns>

public StreamReader GetUrlStream(HttpWebRequest Request)

{

return this.GetUrlStream(Request.RequestUri.AbsoluteUri,Request);

}

/// <summary>

/// Return a the result from an HTTP Url into a string.

/// </summary>

/// <param name="Url">Url to retrieve.</param>

/// <returns></returns>

public string GetUrl(string Url)

{

StreamReader oHttpResponse = this.GetUrlStream(Url);

if (oHttpResponse == null)

return "";

string lcResult = oHttpResponse.ReadToEnd();

oHttpResponse.Close();

return lcResult;

}

/// <summary>

/// Return a the result from an HTTP Url into a string.

/// </summary>

/// <param name="Url">Url to retrieve.</param>

/// <returns></returns>

public byte[] GetUrlBytes(string Url)

{

StreamReader oHttpResponse = this.GetUrlStream(Url);

if (oHttpResponse == null)

{

return null;

}

string lcResult = oHttpResponse.ReadToEnd();

oHttpResponse.Close();

return null;

}

/// <summary>

/// Retrieves URL with events in the OnReceiveData event.

/// </summary>

/// <param name="Url"></param>

/// <param name="BufferSize"></param>

/// <returns></returns>

public string GetUrlEvents(string Url,long BufferSize)

{

StreamReader oHttpResponse = this.GetUrlStream(Url);

if (oHttpResponse == null)

return "";

long lnSize = BufferSize;

if (this.oWebResponse.ContentLength > 0)

lnSize = this.oWebResponse.ContentLength;

else

lnSize = 0;

Encoding enc = Encoding.GetEncoding(1252);

StringBuilder loWriter = new StringBuilder((int) lnSize);

char[] lcTemp = new char[BufferSize];

OnReceiveDataEventArgs oArgs = new OnReceiveDataEventArgs();

oArgs.TotalBytes = lnSize;

lnSize = 1;

int lnCount = 0;

long lnTotalBytes = 0;

while (lnSize > 0)

{

lnSize = oHttpResponse.Read(lcTemp,0,(int) BufferSize);

if (lnSize > 0)

{

loWriter.Append( lcTemp,0,(int) lnSize );

lnCount++;

lnTotalBytes += lnSize;

// *** Raise an event if hooked up

if (this.OnReceiveData != null)

{

/// *** Update the event handler

oArgs.CurrentByteCount = lnTotalBytes;

oArgs.NumberOfReads = lnCount;

oArgs.CurrentChunk = lcTemp;

this.OnReceiveData(this,oArgs);

// *** Check for cancelled flag

if (oArgs.Cancel)

goto CloseDown;

}

}

} // while

CloseDown:

oHttpResponse.Close();

// *** Send Done notification

if (this.OnReceiveData != null && !oArgs.Cancel)

{

// *** Update the event handler

oArgs.Done = true;

this.OnReceiveData(this,oArgs);

}

// return lcHtml;

return loWriter.ToString();

}

public delegate void OnReceiveDataHandler(object sender, OnReceiveDataEventArgs e);

public class OnReceiveDataEventArgs

{

public long CurrentByteCount=0;

public long TotalBytes = 0;

public int NumberOfReads = 0;

public char[] CurrentChunk;

public bool Done = false;

public bool Cancel = false;

}

}

在wwHttp这个类里面,不仅仅可以传送文件AddPostFile方法,还可以传送变量AddPostKey方法。

这样,如果我们要通过代理服务器传送文件,就可以编写如下的代码了:

wwHttp ww = new wwHttp();

ww.ProxyAddress = "202.132.156.124";

ww.PostMode = 2;

ww.AddPostKey("testKey","test");

ww.AddPostFile("myfile",@"D:\Temp\Java\JavaStart\JavaStart2.jar");

string shtml = ww.GetUrlEvents("http://localhost/UploadFileWeb/WebForm1.aspx",409600);

Console.Write(shtml);

小结:

(1)通过Web Services传文件。

(2)如果不需要使用代理,使用WebClient类

(3)如果需要使用代理,使用扩展得到的类wwHttp

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有