IPFS實踐之功能封裝
更多我的博客請關註: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實踐之功能封裝