BMS, Movie, Illustrations, Programming

[C#] Github API の /markdown/raw にPOSTする際は Content-Type と User-Agent ヘッダが必要

結論だけ知りたい人は

この記事のタイトルが結論になっていますので、それを読んで下さい。

また、ソースコードは、この記事の最後の方に載っています。

Github API の /markdown/raw では Content-type ヘッダが必要

http://hacknote.jp/archives/12087/ によると、Content-typeヘッダが必要らしい。なるほどやってみる。

mint@ubuntu:~$ wget --post-data='# Hello\nWorld!' https://api.github.com/markdown/raw
--2017-09-03 00:06:42--  https://api.github.com/markdown/raw
api.github.com (api.github.com) をDNSに問いあわせています... 192.30.255.116, 192.30.255.117
api.github.com (api.github.com)|192.30.255.116|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 415 Unsupported Media Type
2017-09-03 00:06:43 エラー 415: Unsupported Media Type。

mint@ubuntu:~$ wget --post-data='# Hello\nWorld!' --header='Content-type:text/plain' https://api.github.com/markdown/raw
--2017-09-03 00:16:07--  https://api.github.com/markdown/raw
api.github.com (api.github.com) をDNSに問いあわせています... 192.30.255.117, 192.30.255.116
api.github.com (api.github.com)|192.30.255.117|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 176 [text/html]
`raw.5' に保存中

raw.5                     100%[==================================>]     176  --.-KB/s    in 0s      

2017-09-03 00:16:08 (24.2 MB/s) - `raw.5' へ保存完了 [176/176]

mint@ubuntu:~$ cat raw.5
<h1>
<a id="user-content-hellonworld" class="anchor" href="#hellonworld" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Hello\nWorld!</h1>

しかしそこじゃない

wgetは出来たので、C#からもリクエストを叩いてみようとするが・・・

Content-typeを入れてもダメで、出てきたエラーメッセージを見ても原因不明。

System.Net.WebException: サーバーによってプロトコル違反が発生しました. Section=ResponseStatusLine
   場所 System.Net.WebClient.UploadDataInternal(Uri address, String method, Byte[] data, WebRequest& request)
   場所 System.Net.WebClient.UploadData(Uri address, String method, Byte[] data)
   場所 System.Net.WebClient.UploadData(String address, String method, Byte[] data)
   場所 MarkDownToXml.Program.Main(String[] args) 場所 C:\********\Program.cs:行 21
続行するには何かキーを押してください . . .

仕方がないので、requestb.in にPOST投げてリクエストを比較する。上がオプション無しのwget、下がWebClientです。

( wget のデフォルトは application/x-www-form-urlencoded になる、なるほど。)

User-Agent で弾かれるとは思えないし Accept が無いのが原因かな・・・? と思ったら User-Agentでした。Content-type と User-Agent の両方が必要らしいです。以下ソースコード。

using System;
using System.Text;
using System.Net;

class Program
{
    static void Main(string[] args)
    {
        const string url = "https://api.github.com/markdown/raw";

        try
        {
            WebClient wc = new WebClient();

            wc.Headers["Content-Type"] = "text/plain";
            wc.Headers["User-agent"] = "nyaaaaaaaaaaaaaaaaaaaaaan";

            byte[] resData = wc.UploadData(url, "POST", Encoding.UTF8.GetBytes("# Hello\nWorld!"));
            wc.Dispose();

            Console.WriteLine(Encoding.UTF8.GetString(resData));
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

出力はこんな感じ。

<h1>
<a id="user-content-hello" class="anchor" href="#hello" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Hello</h1>
<p>World!</p>

もしHttpClientを使う場合は

こんな感じになるっぽいです。長いのでasyncを使わないならWebClientの方が良さそう。

using System;
using System.Text;
using System.Net.Http;

class Program
{
    static void Main(string[] args)
    {
        string url = "https://api.github.com/markdown/raw";

        try
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.UserAgent.ParseAdd("nyaaaaaaaaaaaaaaaaaaaaaan");

            var stringContent = new StringContent("# Hello\nWorld!", Encoding.UTF8, "text/plain");

            var task = client.PostAsync(new Uri(url), stringContent);
            task.Wait();
            var result = task.Result;

            if (result.IsSuccessStatusCode)
            {
                var task2 = result.Content.ReadAsStringAsync();
                task2.Wait();
                Console.WriteLine(task2.Result);
            }
            else
            {
                Console.WriteLine("StatusCode : " + result.StatusCode);
                Console.WriteLine("ReasonPhrase : " + result.ReasonPhrase);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

User-agentが無いと、こんなエラーが出ます。

System.AggregateException: 1 つ以上のエラーが発生しました。 ---> System.Net.Http.HttpRequestException: この要求の送信中 にエラーが発生しました。 ---> System.Net.WebException: サーバーによってプロトコル違反が発生しました. Section=ResponseStatusLine
   場所 System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)
   場所 System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
   --- 内部例外スタック トレースの終わり ---
   --- 内部例外スタック トレースの終わり ---
   場所 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   場所 System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   場所 System.Threading.Tasks.Task.Wait()
   場所 Program.Main(String[] args) 場所 C:\********\Program.cs:行 19
---> (内部例外 #0) System.Net.Http.HttpRequestException: この要求の送信中にエラーが発生しました。 ---> System.Net.WebException: サーバーによってプロトコル違反が発生しました. Section=ResponseStatusLine
   場所 System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)
   場所 System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
   --- 内部例外スタック トレースの終わり ---<---