1. 程式人生 > >IPFS實踐之功能封裝

IPFS實踐之功能封裝

lse filepath crash include 上傳 cte execute recursive body

更多我的博客請關註:https://kekbin.com/
在IPFS實踐之初體驗中以命令行的方式演示了如何使用IPFS的基礎命令。然而,如果基於IPFS做二次開發應用程序,就需要對這些功能進行封裝。本文介紹兩種調用IPFS功能的方式。

Cmd

以C#為例,采用啟動cmd進程的方式調用命令行,命令行執行程序如下:

public const int WAIT_FOR_EXIT = 2 * 60 * 000;
public const int CRASH_EXIT_CODE = 0xFFFF;
public static int RunExe(string exeFileName, string args, out string output)
{
    output = "";
    int iCode = CRASH_EXIT_CODE;
    if (exeFileName == null)
    {
        return CRASH_EXIT_CODE;
    }
    ProcessStartInfo procInfo = new ProcessStartInfo
    {
        FileName = exeFileName,
        Arguments = args,
        RedirectStandardOutput = true,
        CreateNoWindow = true,
        UseShellExecute = false,
    };
    Process exeProc = null;
    StreamReader reader = null;
    try
    {
        exeProc = Process.Start(procInfo);
        if (exeProc == null)
        {
            return CRASH_EXIT_CODE;
        }
        if (exeProc.StandardOutput != null && exeProc.StandardOutput.BaseStream != null)
        {
            output = exeProc.StandardOutput.ReadToEnd();
        }
        if (exeProc != null)
        {
            if (exeProc.WaitForExit(WAIT_FOR_EXIT))
            {
                iCode = exeProc.ExitCode;
            }
            int cnt = 0;
            while (!exeProc.HasExited && cnt++ < 15)
            {
                Thread.Sleep(5);
            }
            exeProc.Close();
        }
    }
    catch (Exception e)
    {
        iCode = CRASH_EXIT_CODE;
    }
    finally
    {
        DisposeResource(reader);
    }
    return iCode;
}    

有了以上的封裝,那麽執行ipfs add的調用代碼可以是

string args = string.Format("add {0}", filepath)
ScriptTool.RunExe("ipfs", args, out output);

這樣的方式,有幾種弊端:

  • 由於每執行一條命令都需要啟動一個進程,而這些進程是互斥的,在IPFS repo下,有一個repo_lock,導致同時命令多條執行的失敗率很高。
  • 由於命令行執行時,執行完成的輸出都是字符串,處理起來比較麻煩。

Http Api

之前提過,官方提供的go-ipfs實現的demo中提供了http api的功能,也就是說所有的IPFS功能都可以通過http請求的方式來實現,http接口詳見http API docs
以http api的方式執行,可以完美解決cmd的問題:

  • 它無需另起進程,可以使用多線程,同時執行多條指令
  • api的返回是一個json string,方便解析我們所需要的結果參數。

還是以ipfs add為例,接口說明如下:

/api/v0/add
Add a file or directory to ipfs.
Arguments
arg [file]: The path to a file to be added to ipfs. Required: yes.
recursive [bool]: Add directory paths recursively. Default: “false”. Required: no.
quiet [bool]: Write minimal output. Required: no.
quieter [bool]: Write only final hash. Required: no.
silent [bool]: Write no output. Required: no.
progress [bool]: Stream progress data. Required: no.
trickle [bool]: Use trickle-dag format for dag generation. Required: no.
only-hash [bool]: Only chunk and hash - do not write to disk. Required: no.
wrap-with-directory [bool]: Wrap files with a directory object. Required: no.
hidden [bool]: Include files that are hidden. Only takes effect on recursive add. Required: no.
chunker [string]: Chunking algorithm to use. Required: no.
pin [bool]: Pin this object when adding. Default: “true”. Required: no.
raw-leaves [bool]: Use raw blocks for leaf nodes. (experimental). Required: no.
nocopy [bool]: Add the file using filestore. (experimental). Required: no.
fscache [bool]: Check the filestore for pre-existing blocks. (experimental). Required: no.
cid-version [int]: Cid version. Non-zero value will change default of ‘raw-leaves’ to true. (experimental). Default: “0”. Required: no.
hash [string]: Hash function to use. Will set Cid version to 1 if used. (experimental). Default: “sha2-256”. Required: no.
Request Body


Argument “path” is of file type. This endpoint expects a file in the body of the request as ‘multipart/form-data’.
Response
On success, the call to this endpoint will return with 200 and the following body:
{
"Name": "[string]"
"Hash": "[string]"
"Bytes": "[int64]"
"Size": "[string]"
}

所以add一個文件,需要用post的方式,通過mutipart/form-data格式提交文件內容
這個請求的url:
http://localhost:5001/api/v0/add?recursive=false
其中上傳文件夾的時候,recursive參數需要設置為true。

Mutipart上傳代碼如下

public static string HttpMultiPartPost(string url, int timeOut, string path, bool isDir)
{
    string responseContent;
    var webRequest = (HttpWebRequest)WebRequest.Create(url);
    // 邊界符  
    var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
    // 邊界符  
    var beginBoundary = Encoding.ASCII.GetBytes("\r\n" + "--" + boundary + "\r\n");
    // 最後的結束符  
    var endBoundary = Encoding.ASCII.GetBytes("\r\n" + "--" + boundary + "--\r\n");
    // 設置屬性  
    webRequest.Method = "POST";
    webRequest.Timeout = timeOut;
    webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
    var requestStream = webRequest.GetRequestStream();
    if (isDir)
    {
        //發送分割符
        requestStream.Write(beginBoundary, 0, beginBoundary.Length);
        //發送文件夾頭
        const string folderHeaderFormat =
        "Content-Disposition: file; filename=\"{0}\"\r\n" +
        "Content-Type: application/x-directory\r\n\r\n";
        var folderHeader = string.Format(folderHeaderFormat, GetDirectoryName(path));
        var headerbytes = Encoding.UTF8.GetBytes(folderHeader);
        requestStream.Write(headerbytes, 0, headerbytes.Length);
        DirectoryInfo directory = new DirectoryInfo(path);
        FileInfo[] fileInfo = directory.GetFiles();
        foreach (FileInfo item in fileInfo)
        {
            PostFileItem(requestStream, beginBoundary, item, GetDirectoryName(path));
        }
    }
    else
    {
        PostFileItem(requestStream, beginBoundary, new FileInfo(path), null);
    }
    // 寫入最後的結束邊界符  
    requestStream.Write(endBoundary, 0, endBoundary.Length);
    requestStream.Close();
    var httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
    using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(),Encoding.GetEncoding("utf-8")))
    {
        responseContent = httpStreamReader.ReadToEnd();
    }
    httpWebResponse.Close();
    webRequest.Abort();
    return responseContent;
}

private static void PostFileItem(Stream stream, byte[] boundary, FileInfo fileInfo, string dirName)
{
    string name = fileInfo.Name;
    if (!string.IsNullOrEmpty(dirName))
    {
        name = dirName + "/" + name;
    }
    //發送分割符
    stream.Write(boundary, 0, boundary.Length);
    //發送文件頭
    const string headerFormat =
                    "Abspath: {0}\r\n" +
                    "Content-Disposition: file; filename=\"{1}\"\r\n" +
                    "Content-Type: application/octet-stream\r\n\r\n";
    var header = string.Format(headerFormat, fileInfo.FullName, name);
    var headerbytes = Encoding.UTF8.GetBytes(header);
    stream.Write(headerbytes, 0, headerbytes.Length);
    //發送文件流
    var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
    var buffer = new byte[1024];
    int bytesRead; // =0  
    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
    {
        stream.Write(buffer, 0, bytesRead);
    }
    fileStream.Close();
}

add Api調用代碼如下:

public static string Add(string path, bool isDir)
{
    if (string.IsNullOrEmpty(path))
    {
                return null;
    }
    string format = "";
    if (isDir)
    {
        format = "{0}?chunker=size-262144&recursive=true";
    }
    else
    {
        //如果需要保存名字,需要wrap一個dir
        //format = "{0}?chunker=size-262144&recursive=false&wrap-with-directory=true";
        format = "{0}?chunker=size-262144&recursive=false";
    }
    string url = string.Format(format, "http://localhost:5001/api/v0/add");
    string output = HttpMultiPartPost(url, 500000, path, isDir);
    return output;
}

大部分的api都是以get的方式請求,相比add簡單許多,這裏提供一個C#的httpget方法供參考.

public static string HttpGet(string url, int timeOut)
{
    string responseContent;
    var webRequest = (HttpWebRequest)WebRequest.Create(url);
    webRequest.Method = "GET";
    webRequest.Timeout = timeOut;
    var httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
    using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.GetEncoding("utf-8")))
    {
        responseContent = httpStreamReader.ReadToEnd();
    }
    httpWebResponse.Close();
    webRequest.Abort();
    return responseContent;
}

如name publish這樣的操作就可以這樣調用

public static string NamePublish(string hash, int hours)
{
    //lifetime失效期
    string format = "{0}?arg={1}&lifetime={2}h";
    string url = string.Format(format, @"http://localhost:5001/api/v0/name/publish", hash, hours);
    return HttpTool.HttpGet(url, 100000);
}

封裝了這些操作,我們就可以將IPFS用起來,做一些我們所需要的application。

IPFS實踐之功能封裝