27

私はリンクチェッカーに取り組んでいます。通常はHEADリクエストを実行できますが、一部のサイトではこの動詞が無効になっているようです。そのため、失敗した場合はGETリクエストも実行する必要があります (リンクが実際に無効であることを再確認するため)。

リンクテスターとして次のコードを使用します。

public class ValidateResult
{
  public HttpStatusCode? StatusCode { get; set; }
  public Uri RedirectResult { get; set; }
  public WebExceptionStatus? WebExceptionStatus { get; set; }
}


public ValidateResult Validate(Uri uri, bool useHeadMethod = true, 
            bool enableKeepAlive = false, int timeoutSeconds = 30)
{
  ValidateResult result = new ValidateResult();

  HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
  if (useHeadMethod)
  {
    request.Method = "HEAD";
  }
  else
  {
    request.Method = "GET";
  }

  // always compress, if you get back a 404 from a HEAD it can be quite big.
  request.AutomaticDecompression = DecompressionMethods.GZip;
  request.AllowAutoRedirect = false;
  request.UserAgent = UserAgentString;
  request.Timeout = timeoutSeconds * 1000;
  request.KeepAlive = enableKeepAlive;

  HttpWebResponse response = null;
  try
  {
    response = request.GetResponse() as HttpWebResponse;

    result.StatusCode = response.StatusCode;
    if (response.StatusCode == HttpStatusCode.Redirect ||
      response.StatusCode == HttpStatusCode.MovedPermanently ||
      response.StatusCode == HttpStatusCode.SeeOther)
    {
      try
      {
        Uri targetUri = new Uri(Uri, response.Headers["Location"]);
        var scheme = targetUri.Scheme.ToLower();
        if (scheme == "http" || scheme == "https")
        {
          result.RedirectResult = targetUri;
        }
        else
        {
          // this little gem was born out of http://tinyurl.com/18r 
          // redirecting to about:blank
          result.StatusCode = HttpStatusCode.SwitchingProtocols;
          result.WebExceptionStatus = null;
        }
      }
      catch (UriFormatException)
      {
        // another gem... people sometimes redirect to http://nonsense:port/yay
        result.StatusCode = HttpStatusCode.SwitchingProtocols;
        result.WebExceptionStatus = WebExceptionStatus.NameResolutionFailure;
      }

    }
  }
  catch (WebException ex)
  {
    result.WebExceptionStatus = ex.Status;
    response = ex.Response as HttpWebResponse;
    if (response != null)
    {
      result.StatusCode = response.StatusCode;
    }
  }
  finally
  {
    if (response != null)
    {
      response.Close();
    }
  }

  return result;
}

これはすべてうまく機能します。GETリクエストを実行すると、ペイロード全体がダウンロードされることを除いて(私はこれをwiresharkで見ました)。

応答本文をバッファリングまたは熱心にロードしないServicePointように、基礎となるものを構成する方法はありますか?HttpWebRequest

(これを手作業でコーディングしていた場合、TCP 受信ウィンドウを非常に低く設定し、ヘッダーを取得するのに十分なパケットのみを取得し、十分な情報が得られたらすぐに TCP パケットの ack を停止します。)

これが何を達成しようとしているのか疑問に思っている人のために、404 を取得したときに 40k の 404 をダウンロードしたくありません。これを数十万回行うと、ネットワークに負荷がかかります。

4

3 に答える 3

8

GET を実行すると、サーバーはファイルの先頭から末尾までデータの送信を開始します。中断しない限り。確かに、10 Mb/秒では、1 秒あたり 1 メガバイトになるため、ファイルが小さい場合はすべてを取得できます。いくつかの方法で、実際にダウンロードする量を最小限に抑えることができます。

request.Abortまず、応答を取得した後、呼び出す前に呼び出すことができますresponse.close。これにより、基になるコードが応答を閉じる前にすべてをダウンロードしようとしないことが保証されます。これが小さなファイルに役立つかどうかはわかりません。マルチギガバイトのファイルをダウンロードしようとしているときに、アプリケーションがハングするのを防ぐことができることは知っています.

他にできることは、ファイル全体ではなく範囲を要求することです。AddRangeメソッドとそのオーバーロードを参照してください。たとえば、request.AddRange(512)ファイルの最初の 512 バイトのみをダウンロードする と書くことができます。もちろん、これは範囲クエリをサポートするサーバーに依存します。ほとんどがそうします。ただし、ほとんどの場合、HEAD リクエストもサポートされます。

おそらく、順番に物事を試すメソッドを書かなければならなくなるでしょう:

  • HEAD リクエストを実行してみてください。それが機能する場合 (つまり、500 が返されない場合)、完了です。
  • 範囲クエリで GET を試してください。500 が返されない場合は、完了です。
  • request.AbortafterGetResponseリターンを使用して、通常の GET を実行します。
于 2012-05-25T14:17:05.223 に答える
1

GETリクエストを使用している場合は、必要かどうかに関係なく、メッセージ本文を受け取ります。データは、ソケットから読み取ったかどうかに関係なく、エンドポイントに送信されます。データは、選択されるのを待っているRecvQのキューにとどまります。

このためには、可能であれば「HEAD」リクエストを使用する必要があります。これにより、メッセージ本文を節約できます。

于 2012-05-25T12:14:22.203 に答える
-1

WebClientを使用してストリームを開き、必要な数バイトだけを読み取ることができませんでしたか?

using (var client = new WebClient())
        {
            using (var stream = client.OpenRead(uri))
            {
                const int chunkSize = 100;
                var buffer = new byte[chunkSize];
                int bytesRead;
                while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    //check response here
                }
            }
        }

WebClientがストリームを内部で開く方法がわかりません。しかし、それはデータの部分的な読み取りを可能にするようです。

于 2012-05-25T04:23:53.313 に答える