{"id":5396,"date":"2018-02-18T13:00:05","date_gmt":"2018-02-18T13:00:05","guid":{"rendered":"http:\/\/writeasync.net\/?p=5396"},"modified":"2018-02-14T03:36:00","modified_gmt":"2018-02-14T03:36:00","slug":"a-real-async-getfiles","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5396","title":{"rendered":"A real async GetFiles?"},"content":{"rendered":"<p>I&#8217;ve lamented in the past that there is <a href=\"http:\/\/writeasync.net\/?p=2621\">no real async GetFiles<\/a>. But that&#8217;s okay &#8212; we&#8217;re problem solvers! Perhaps if we could drop down to the core native API, we could fill in a gap like this. Let&#8217;s start by figuring out how GetFiles is implemented in .NET. Searching in the .NET Core source code, we eventually find a <a href=\"https:\/\/github.com\/dotnet\/corefx\/blob\/66ada723c1b0ae9062a0e7b0b28862014b985019\/src\/System.IO.FileSystem\/src\/System\/IO\/Enumeration\/FileSystemEnumerator.Win32.cs\">FileSystemEnumerator class for Win32<\/a> which calls <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/content\/ntifs\/nf-ntifs-ntquerydirectoryfile\">NtQueryDirectoryFile<\/a>.<\/p>\n<p>Ah, but there are some problems. First, since this is an API from the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Native_API\">NT native layer<\/a>, we are not working with the kinds of constructs that we are used to, such as <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms684342(v=vs.85).aspx\">OVERLAPPED structures<\/a> and the like.  (From previous experience, we know that <a href=\"http:\/\/writeasync.net\/?p=5191\">overlapped I\/O is actually quite doable in .NET<\/a>.) Instead, we would have to do some sort of <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms681951(v=vs.85).aspx\">complex APC magic<\/a> &#8212; not so straightforward in managed code! Second, it seems this might not even work if we tried, if <a href=\"https:\/\/stackoverflow.com\/questions\/5281806\/asynchronous-ntquerydirectoryfile\">the wisdom of Stackoverflow<\/a> is to be believed.<\/p>\n<p>Do we give up hope? Not necessarily! Checking the documentation for the structures returned from NtQueryDirectoryFile (say, <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/content\/ntifs\/ns-ntifs-_file_full_dir_information\">FILE_FULL_DIR_INFORMATION<\/a>), we see that there is another way to get this information. Specifically, we could &#8220;create an IRP with major function code IRP_MJ_DIRECTORY_CONTROL and minor function code IRP_MN_QUERY_DIRECTORY.&#8221; I guess that clears it up, aside from three questions I would have about that sentence.<\/p>\n<p>An IRP (pronounced <a href=\"https:\/\/perl.plover.com\/yak\/dirty\/samples\/slide002.html\">&#8220;urp&#8221;<\/a>) is an <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/hardware\/ff550694(v=vs.85).aspx\">I\/O request packet<\/a>. These are mainly used by drivers to perform requests on the file system or network. IRP_MJ_DIRECTORY_CONTROL is <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/kernel\/irp-major-function-codes\">major function code<\/a> which tells a driver what general type of function needs to be performed (think: &#8220;class&#8221;) &#8212; in this case it would refer to the set of directory handling routines. IRP_MN_QUERY_DIRECTORY is a minor function code which exactly describes the requested operation (think: &#8220;method&#8221;)<\/p>\n<p>This is all well and good, but how would we even send an IRP from user mode? Unfortunately, there aren&#8217;t any ways to send arbitrary requests. We have <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/aa363216(v=vs.85).aspx\">DeviceIoControl<\/a>, but that sends <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ifs\/irp-mj-device-control\">IRP_MJ_DEVICE_CONTROL<\/a> and does not support the directory query command we want. There are also some WMI functions to send IRPs (e.g. the command line <code>\"wmic.exe sysdriver ... call ...\"<\/code>) but this is only for <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/kernel\/irp-mj-system-control\">IRP_MJ_SYSTEM_CONTROL<\/a>.<\/p>\n<p>It seems that all signs are pointing to &#8220;figure out this APC thing.&#8221; Before attempting this in managed code, let&#8217;s take a look at a possible implementation in native C++. The basic idea will be to use <a href=\"http:\/\/writeasync.net\/?p=1901\">PPL<\/a> to define a real-ish async &#8220;get files&#8221; operation that returns a task holding a list (<code>vector<\/code>) of path names (<code>wstring<\/code>). I say <em>real-ish<\/em> because the use of APCs means that we will be forced to stay on the same thread to query the information and handle the async callback. Yes, I realize that means this is hardly worth it, but we&#8217;re doing this just to show that we can!<\/p>\n<p>First things first: we need to <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/download-the-wdk\">install the Windows Driver Kit<\/a>. It contains the necessary headers and libraries to call NT APIs without a lot of hassle. After that, we can create a simple static library project to help isolate all the NT junk from our main application &#8212; mixing <code>Windows.h<\/code> and NT headers doesn&#8217;t really work out well. Note that the WDK adds several Visual Studio templates for creating driver projects, but we are <em>not<\/em> going to use one here, as we are writing user mode code that just happens to use NT APIs. Because of this, we&#8217;ll have to add a few project settings to make the compiler happy:<\/p>\n<ul>\n<li><strong>C\/C++ : Additional Include Directories<\/strong>: add <code>$(FrameworkSdkDir)\\Include\\$(TargetPlatformVersion)\\km\\<\/code><\/li>\n<li><strong>C\/C++ : Preprocessor Definitions<\/strong>: add <code>_AMD64_<\/code> and make sure you are compiling for the x64 platform<\/li>\n<\/ul>\n<p>To help visualize the operation, we will create a simple log callback function definition. It will be used by the inner operation later:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ LogCallback.h\r\n#pragma once\r\n\r\nnamespace nt\r\n{\r\n    typedef void(*LogCallback)(const wchar_t* message);\r\n}\r\n<\/pre>\n<p>Now we&#8217;ll define a <a href=\"https:\/\/stackoverflow.com\/questions\/26546265\/what-is-the-detail-namespace-commonly-used-for\">detail header\/namespace<\/a> to hold all the gory details:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ detail.h\r\n#pragma once\r\n\r\n#include &quot;LogCallback.h&quot;\r\n#include &lt;sstream&gt;\r\n#include &lt;ppltasks.h&gt;\r\n#include &lt;ntifs.h&gt;\r\n\r\nnamespace nt\r\n{\r\n    namespace detail\r\n    {\r\n        class NTError : public std::runtime_error\r\n        {\r\n        public:\r\n            NTError(NTSTATUS status)\r\n                : runtime_error(Message(status))\r\n            {\r\n            }\r\n\r\n            static std::string Message(NTSTATUS status)\r\n            {\r\n                std::stringstream ss;\r\n                ss &lt;&lt; &quot;Error 0x&quot; &lt;&lt; std::hex &lt;&lt; status;\r\n                return ss.str();\r\n            }\r\n        };\r\n\r\n        class DirectoryHandle\r\n        {\r\n        private:\r\n            HANDLE handle_;\r\n\r\n        public:\r\n            DirectoryHandle(const std::wstring&amp; path)\r\n                : handle_(Open(path.c_str()))\r\n            { }\r\n\r\n            HANDLE get() const\r\n            {\r\n                return handle_;\r\n            }\r\n\r\n            ~DirectoryHandle()\r\n            {\r\n                NtClose(handle_);\r\n            }\r\n\r\n        private:\r\n            static HANDLE Open(LPCWSTR path)\r\n            {\r\n                UNICODE_STRING name;\r\n                RtlInitUnicodeString(&amp;name, path);\r\n                OBJECT_ATTRIBUTES attributes;\r\n                InitializeObjectAttributes(&amp;attributes, &amp;name, 0, nullptr, nullptr);\r\n                HANDLE handle;\r\n                IO_STATUS_BLOCK statusBlock = { 0 };\r\n                NTSTATUS status = NtOpenFile(\r\n                    &amp;handle,\r\n                    FILE_LIST_DIRECTORY,\r\n                    &amp;attributes,\r\n                    &amp;statusBlock,\r\n                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\r\n                    FILE_DIRECTORY_FILE);\r\n                if (status != STATUS_SUCCESS)\r\n                {\r\n                    throw NTError(status);\r\n                }\r\n\r\n                return handle;\r\n            }\r\n\r\n            DirectoryHandle &amp; operator=(const DirectoryHandle&amp; other) = delete;\r\n            DirectoryHandle(const DirectoryHandle&amp; other) = delete;\r\n        };\r\n\r\n        class GetFilesOperation\r\n        {\r\n        private:\r\n            LogCallback log_;\r\n            DirectoryHandle handle_;\r\n            std::wstring path_;\r\n            UCHAR buffer_&#x5B;1024];\r\n            std::vector&lt;std::wstring&gt; files_;\r\n            concurrency::task_completion_event&lt;std::vector&lt;std::wstring&gt;&gt; taskEvent_;\r\n\r\n        public:\r\n            GetFilesOperation(LogCallback log, const std::wstring&amp; absolutePath)\r\n                : log_(log),\r\n                handle_(L&quot;\\\\DosDevices\\\\&quot; + absolutePath),\r\n                path_(absolutePath),\r\n                buffer_(),\r\n                files_(),\r\n                taskEvent_()\r\n            {\r\n            }\r\n\r\n            concurrency::task&lt;std::vector&lt;std::wstring&gt;&gt; RunAsync()\r\n            {\r\n                concurrency::create_task(&#x5B;]() {}).then(&#x5B;this]() { Run(); });\r\n                return concurrency::create_task(taskEvent_);\r\n            }\r\n\r\n        private:\r\n            static void NTAPI OnCompleted(\r\n                _In_ PVOID ApcContext,\r\n                _In_ PIO_STATUS_BLOCK \/* IoStatusBlock *\/,\r\n                _In_ ULONG \/* Reserved *\/)\r\n            {\r\n                static_cast&lt;GetFilesOperation*&gt;(ApcContext)-&gt;Log(L&quot;&lt;OnCompleted&gt;&quot;);\r\n            }\r\n\r\n            GetFilesOperation &amp; operator=(const GetFilesOperation&amp; other) = delete;\r\n            GetFilesOperation(const GetFilesOperation&amp; other) = delete;\r\n\r\n            void Log(LPCWSTR message) const\r\n            {\r\n                if (log_)\r\n                {\r\n                    log_(message);\r\n                }\r\n            }\r\n\r\n            void Run()\r\n            {\r\n                try\r\n                {\r\n                    bool done;\r\n                    do\r\n                    {\r\n                        done = Next();\r\n                    } while (!done);\r\n                }\r\n                catch (const std::exception&amp; e)\r\n                {\r\n                    taskEvent_.set_exception(e);\r\n                }\r\n\r\n                taskEvent_.set(files_);\r\n            }\r\n\r\n            bool Next()\r\n            {\r\n                return Query() || ReadEntries();\r\n            }\r\n\r\n            bool Query()\r\n            {\r\n                RtlZeroMemory(buffer_, sizeof(buffer_));\r\n                IO_STATUS_BLOCK statusBlock = { 0 };\r\n                Log(L&quot;&lt;NtQueryDirectoryFile&gt;&quot;);\r\n                NTSTATUS status = NtQueryDirectoryFile(\r\n                    handle_.get(),\r\n                    nullptr,\r\n                    OnCompleted,\r\n                    this,\r\n                    &amp;statusBlock,\r\n                    buffer_,\r\n                    sizeof(buffer_),\r\n                    FileNamesInformation,\r\n                    FALSE,\r\n                    nullptr,\r\n                    FALSE);\r\n\r\n                switch (status)\r\n                {\r\n                case STATUS_PENDING:\r\n                    Log(L&quot;&lt;ZwWaitForSingleObject&gt;&quot;);\r\n                    ZwWaitForSingleObject(NtCurrentThread(), TRUE, nullptr);\r\n                    break;\r\n                default:\r\n                    throw NTError(status);\r\n                }\r\n\r\n                bool done = static_cast&lt;size_t&gt;(statusBlock.Information) == 0;\r\n                if (done)\r\n                {\r\n                    Log(L&quot;&lt; done! &gt;&quot;);\r\n                }\r\n\r\n                return done;\r\n            }\r\n\r\n            bool ReadEntries()\r\n            {\r\n                size_t offset = 0;\r\n                do\r\n                {\r\n                    FILE_NAMES_INFORMATION* fileInfo = reinterpret_cast&lt;FILE_NAMES_INFORMATION*&gt;(&amp;buffer_&#x5B;offset]);\r\n                    std::wstring fileName(fileInfo-&gt;FileName, fileInfo-&gt;FileNameLength \/ 2);\r\n                    std::wstring fullPath(path_ + L&quot;\\\\&quot; + fileName);\r\n                    files_.push_back(fullPath);\r\n\r\n                    ULONG next = fileInfo-&gt;NextEntryOffset;\r\n                    if (next == 0)\r\n                    {\r\n                        return false;\r\n                    }\r\n\r\n                    offset += next;\r\n                } while (true);\r\n            }\r\n        };\r\n    }\r\n}\r\n<\/pre>\n<p>There is a lot going on here, but here is the gist:<\/p>\n<ul>\n<li><code>DirectoryHandle<\/code> holds an open handle to the directory object, retrieved using the native <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/content\/ntifs\/nf-ntifs-ntopenfile\">NtOpenFile<\/a> call. We are not specifying the usual synchronous flags (see <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/content\/wdm\/nf-wdm-zwcreatefile\">NtCreateFile<\/a>), so we will expect all underlying calls to complete asynchronously.<\/li>\n<li><code>GetFilesOperation<\/code> has all the actual query logic. Note that it passes the special <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/kernel\/introduction-to-ms-dos-device-names\">&#8220;DosDevices&#8221; namespace<\/a> when opening the handle since we&#8217;re dealing with NT native API and have to be specific.<\/li>\n<li>Before we start enumerating in <code>GetFilesOperation::RunAsync<\/code>, we have to get off the main thread, which we accomplish by using <code>task::then<\/code>, which schedules a continuation on the thread pool. This ensures that we can block while waiting for APC callbacks.<\/li>\n<li>Now we get to the more interesting <code>GetFilesOperation::Run<\/code> method which eventually calls into all the real NT stuff. It calls the inner <code>Next<\/code> method in a loop, taking care to handle exceptions and set the task result.<\/li>\n<li><code>Next<\/code> is just a thin wrapper around <code>Query<\/code> (which finally calls the directory query and waits for the APC) and <code>ReadEntries<\/code> (which interprets the data).<\/li>\n<\/ul>\n<p>Now, for the &#8220;public&#8221; API, a wrapper class called <code>AsyncDirectory<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ AsyncDirectory.h\r\n#pragma once\r\n\r\n#include &quot;LogCallback.h&quot;\r\n#include &lt;string&gt;\r\n#include &lt;ppltasks.h&gt;\r\n\r\nnamespace nt\r\n{\r\n    class AsyncDirectory\r\n    {\r\n    private:\r\n        std::wstring path_;\r\n        LogCallback log_;\r\n\r\n    public:\r\n        AsyncDirectory(const std::wstring&amp; absolutePath, LogCallback log = nullptr);\r\n\r\n        concurrency::task&lt;std::vector&lt;std::wstring&gt;&gt; GetFilesAsync() const;\r\n    };\r\n}\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ AsyncDirectory.cpp\r\n#include &quot;AsyncDirectory.h&quot;\r\n#include &quot;detail.h&quot;\r\n#include &lt;memory&gt;\r\n\r\nusing namespace concurrency;\r\nusing namespace nt;\r\nusing namespace nt::detail;\r\nusing namespace std;\r\n\r\nAsyncDirectory::AsyncDirectory(const std::wstring&amp; absolutePath, LogCallback log)\r\n    : path_(absolutePath),\r\n    log_(log)\r\n{ }\r\n\r\ntask&lt;vector&lt;wstring&gt;&gt; AsyncDirectory::GetFilesAsync() const\r\n{\r\n    auto op = make_shared&lt;GetFilesOperation&gt;(log_, path_);\r\n    return op-&gt;RunAsync().then(&#x5B;op](task&lt;vector&lt;wstring&gt;&gt; t) { return t; });\r\n}\r\n<\/pre>\n<p>The only semi-interesting thing here is that we have to keep <code>GetFilesOperation<\/code> alive until we return the final result. This is achieved by using a <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/standard-library\/shared-ptr-class\">shared_ptr<\/a> which is captured in a pass-through continuation at the end, ensuring its lifetime.<\/p>\n<p>Whew. It was lots of effort, but amazingly, this actually works! Below is a sample app which references the above static lib and reads the files in a test directory I prepared beforehand. To ensure this compiles and links properly, make sure the include path points to the static lib header folder and put <code>ntdll.lib<\/code> as an additional dependency in the Linker options.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;AsyncDirectory.h&quot;\r\n#include &lt;Windows.h&gt;\r\n#include &lt;iostream&gt;\r\n\r\nusing namespace concurrency;\r\nusing namespace nt;\r\nusing namespace std;\r\n\r\ndouble Now()\r\n{\r\n    LARGE_INTEGER c;\r\n    QueryPerformanceCounter(&amp;c);\r\n    LARGE_INTEGER f;\r\n    QueryPerformanceFrequency(&amp;f);\r\n    return (double)c.QuadPart \/ f.QuadPart;\r\n}\r\n\r\nint ElapsedMilliseconds()\r\n{\r\n    static double Start = Now();\r\n    return static_cast&lt;int&gt;(1000 * (Now() - Start));\r\n}\r\n\r\nvoid Log(const wchar_t* message)\r\n{\r\n    wcout &lt;&lt; L&quot;&#x5B;&quot; &lt;&lt; ElapsedMilliseconds() &lt;&lt; L&quot;, T=&quot; &lt;&lt; GetCurrentThreadId() &lt;&lt; L&quot;] &quot; &lt;&lt; message &lt;&lt; endl;\r\n}\r\n\r\nint main()\r\n{\r\n    Log(L&quot;Starting.&quot;);\r\n    AsyncDirectory dir(L&quot;G:\\\\temp\\\\dir&quot;, Log);\r\n\r\n    Log(L&quot;Getting files...&quot;);\r\n    task&lt;vector&lt;wstring&gt;&gt; task = dir.GetFilesAsync();\r\n    auto files = task.get();\r\n    for (auto it = files.cbegin(); it != files.cend(); ++it)\r\n    {\r\n        Log(it-&gt;c_str());\r\n    }\r\n\r\n    return 0;\r\n}\r\n<\/pre>\n<p>The output shows that everything functions basically as we&#8217;d expect:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;0, T=13992] Starting.\r\n&#x5B;0, T=13992] Getting files...\r\n&#x5B;1, T=19740] &lt;NtQueryDirectoryFile&gt;\r\n&#x5B;1, T=19740] &lt;ZwWaitForSingleObject&gt;\r\n&#x5B;2, T=19740] &lt;OnCompleted&gt;\r\n&#x5B;2, T=19740] &lt;NtQueryDirectoryFile&gt;\r\n&#x5B;3, T=19740] &lt;ZwWaitForSingleObject&gt;\r\n&#x5B;4, T=19740] &lt;OnCompleted&gt;\r\n&#x5B;4, T=19740] &lt;NtQueryDirectoryFile&gt;\r\n&#x5B;5, T=19740] &lt;ZwWaitForSingleObject&gt;\r\n&#x5B;5, T=19740] &lt;OnCompleted&gt;\r\n&#x5B;6, T=19740] &lt; done! &gt;\r\n&#x5B;6, T=13992] G:\\temp\\dir\\.\r\n&#x5B;7, T=13992] G:\\temp\\dir\\..\r\n&#x5B;7, T=13992] G:\\temp\\dir&#92;&#48;0\r\n&#x5B;8, T=13992] G:\\temp\\dir&#92;&#48;1\r\n&#x5B;9, T=13992] G:\\temp\\dir&#92;&#48;2\r\n&#x5B;9, T=13992] G:\\temp\\dir&#92;&#48;3\r\n&#x5B;10, T=13992] G:\\temp\\dir&#92;&#48;4\r\n . . . \r\n&#x5B;82, T=13992] G:\\temp\\dir\\96\r\n&#x5B;83, T=13992] G:\\temp\\dir\\97\r\n&#x5B;84, T=13992] G:\\temp\\dir\\98\r\n&#x5B;85, T=13992] G:\\temp\\dir\\99\r\n<\/pre>\n<p>Obviously there are about 1000 caveats here. Not all potential errors are handled. The actual asynchrony of this mechanism is highly debatable &#8212; we&#8217;re switching threads and using alertable waits, so every operation still burns one thread, just like it would have for the synchronous version. Yet, it does show that an async GetFiles is at least <em>achievable<\/em>. I have a feeling we can further explore this space and come up with something more useful, however. But more on that at a later time&#8230;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve lamented in the past that there is no real async GetFiles. But that&#8217;s okay &#8212; we&#8217;re problem solvers! Perhaps if we could drop down to the core native API, we could fill in a gap like this. Let&#8217;s start&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,101],"tags":[],"class_list":["post-5396","post","type-post","status-publish","format-standard","hentry","category-async","category-native"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5396","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5396"}],"version-history":[{"count":6,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5396\/revisions"}],"predecessor-version":[{"id":5407,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5396\/revisions\/5407"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5396"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5396"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5396"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}