トップC# > ファイルのアップロード

ファイルのアップロード

WebClientクラスのUploadFileメソッド

ファイルのアップロードはC#では簡単である。 ID、パスワードでアクセス管理されているサーバーへアップロードするには、次のようにすればよい。

 WebClient wc = new WebClient();
 wc.Credentials = new NetworkCredential("id", "password");
 wc.UploadFile(uri, localpath);

NetworkCredentialコンストラクタの引数にはアクセス権のあるIDとパスワードを文字列型で与える。

ディレクトリの作成とディレクトリ/ファイルの削除

ディレクトリの作成は FTPサーバー限定である。

ディレクトリの作成の方が少し面倒である。 アップロードと同じWebClientクラスのインスタンスで実行できた方がありがたいのであるが、 マニュアルをざっとみた限りでは、該当機能が見つからなかった。

WebRequestクラスを使って、次のようにする。

ディレクトリの削除はMethodプロパティに WebRequestMethods.Ftp.RemoveDirectory を指定する。

ファイルの削除はMethodプロパティに WebRequestMethods.Ftp.DeleteFile を指定する。

 WebRequest request = WebRequest.Create(uri);
 request.Method = WebRequestMethods.Ftp.MakeDirectory;
 request.Credentials = new NetworkCredential("id", "password");
 using (var resp = (FtpWebResponse)request.GetResponse()) {
     Console.WriteLine(resp.StatusCode);
 }

FTPサーバーへのアップロードプログラム

FTPサイトのディレクトリやファイルの照合は行わず、 前回アップロードを実行した時刻以降に作成、更新したファイルのみアップロードしている。 新たなディレクトリがあれば、FTPサイトに作成している。

したがって、ディレクトリやファイルの削除には対応していない。 これは、削除が少ないことと、削除に対応するとプログラムの実行時間が長くなることによる。

新規ディレクトリが見つかると、ファイルのアップロードとは別に ディレクトリ毎にWebRequestインスタンスを生成して、FTPサイトにディレクトリを作成している。 多くの新規ディレクトリが存在する場合、効率が悪いことになるが、実際の運用では、 ファイルの新規作成や更新が殆どであり、ディレクトリの作成はごくまれなため、問題にならない。

// upload.cs  2023.1.19/2020.3.21/2013.3.9
// 前回アップロードした時刻以降で更新または作成された
// ファイルをサーバーにアップロードする。
// csc /out:c:\_wwwsys\bin\upload.exe c:\_wwwsys\src\upload.cs

using System;
using System.IO;
using System.Net;

class Upload {
    const string DIR = @"c:\_www";
    const string TimeStampFile = "c:/_wwwsys/timestamp.dat";
    string srv, id, pw;
    DateTime dtLast;
    WebClient wc;

    public Upload() {
        Console.Write("password:");
        pw = Console.ReadLine();
        if (File.Exists(TimeStampFile)) {
            dtLast = File.GetLastWriteTime(TimeStampFile);  // 前回のアップロード時間
            File.SetLastWriteTime(TimeStampFile, DateTime.Now);  // タイムスタンプ更新
        } else {
            dtLast = new DateTime(1,1,1);
            File.WriteAllText(TimeStampFile, "");
        }
        try {
            srv = "ftp://htdmnr.starfree.jp";
            id = "htdmnr.starfree.jp";
            wc = new WebClient();
            wc.Credentials = new NetworkCredential(id, pw);
            scan(DIR);
            wc.Dispose();
        } catch (Exception e) {
            Console.WriteLine(e);
        }
    }

    void scan(string dir) {
        foreach (string path in Directory.GetFiles(dir)) {
            string uri = srv + path.Substring(DIR.Length).Replace(@"\", "/");
            DateTime dtCR = Directory.GetCreationTime(path);
            DateTime dtLW = Directory.GetLastWriteTime(path);
            if (dtCR > dtLast || dtLW > dtLast) {  // 更新または新規ファイル
                try {
                    byte[] response = wc.UploadFile(uri, path);
                    Console.WriteLine("upload: " + path + " " 
                        + System.Text.Encoding.ASCII.GetString(response));
                } catch (Exception e) {
                    System.Windows.Forms.MessageBox.Show(e.ToString());
                }
            }
        }
        foreach (string path in Directory.GetDirectories(dir)) {
            string uri = srv + path.Substring(DIR.Length).Replace(@"\", "/");
            DateTime dt  = Directory.GetCreationTime(path);
            if (dt > dtLast) {
                Console.WriteLine("新規ディレクトリ: " + uri);
                try {
                    WebRequest request = WebRequest.Create(uri);
                    request.Method = WebRequestMethods.Ftp.MakeDirectory;
                    request.Credentials = new NetworkCredential(id, pw);
                    using (var resp = (FtpWebResponse)request.GetResponse()) {
                        Console.WriteLine(resp.StatusCode);
                    }
                } catch (Exception ex) {
                    Console.WriteLine(ex);
                }
            }
            scan(path);
        }
    }

    static void Main() {
        new Upload();
        System.Threading.Thread.Sleep(1000);
    }

}

POSTでWebサーバーへデータを送信する

ファイル query.txt のサイズが小さい時は 次のようにしてデータをサーバーへ送信できる。

curl --globoff -o output.xml -d @c:/osm/query.txt http://overpass-api.de/api/interpreter

curlコマンドでは、送信データを一旦メモリに読み込むようであり、Postするファイルサイズが大きい場合、 メモリ不足になる。

記事[1]を参考にして、次のプログラムを作成した。

using System;
using System.IO;
using System.Net;

class MyWebClient : WebClient {
    protected override WebRequest GetWebRequest(Uri address)  {
        var w = base.GetWebRequest(address);
        w.Timeout = 60 * 60 * 1000;
        return w;
    }
}

class Post {
    static void Main(string[] args) {
        string url = "http://overpass-api.de/api/interpreter";
        string str = File.ReadAllText("c:/osm/nodes.txt", System.Text.Encoding.GetEncoding("utf-8"));

        MyWebClient wc = new MyWebClient();

        //データを送信し、また受信する
        string resText = wc.UploadString(url, str);
        wc.Dispose();

        //受信したデータを表示する
        Console.WriteLine(resText);
    }
}

しかし、次のエラーとなった。

ハンドルされていない例外: System.Net.WebException: リモート サーバーがエラーを返しました: (400) 要求が不適切です
   場所 System.Net.WebClient.UploadDataInternal(Uri address, String method, Byte[] data, WebRequest& request)
   場所 System.Net.WebClient.UploadString(Uri address, String method, String data)
   場所 Post.Main(String[] args)

curl コマンドでの --globoff は送信データに [ ] や { } があることを伝えるもの[2]であり、 WebClient では考慮が要らない。

node数が多すぎるためかも知れない。まず リソースの上限の設定方法に問題がありそうである。

Overpass XML では element-limit があるが、Overpass QL にはないようだ。maxsize はある。要素数ではなく、 メモリサイズかも知れない。

リソースの制限がない場合、data=node(1);out; で良かったが、リソースの上限を設定する場合、「data=」を 取って、 [timeout:3600][maxsize:2073741824]; node(id:123456,234567, .... , 987654);

リファレンス

[1] POSTでデータを送信する
[2] curlで[]や{}を含む場合は--globoffを使用する