使用Debug Api编写ApiMonitor

简介:

Pnig0s1992 p.s:

使用编写调试器的一组DebugApi编写了一个ApiMonitor的雏形,可以对正在进行的进程,或者直接启动被监控程序进行监控。特点在于所有的Api签名全部写到了xml文件中,可以从外部进行导入,增强了定制性。成品用到项目中了,这里只实现个基本思想,核心代码采用C/C++编写,后来项目需要又通过Invoke,.Net互操作,因为C代码中是一个动态的调试循环所以没法写成Dll,只能把所有代码全部移植到C#平台下~这里贴出来C代码吧,一个Header.h一个MonApi.cpp,能力有限,错误在所难免,欢迎批评指正~

 

Header.h:

 
  1. #include <Windows.h>  
  2. #include <stdio.h>  
  3. #include <map>  
  4. #include <queue>  
  5. #include <string>  
  6. #include <stack>  
  7.  
  8. using namespace std;  
  9.  
  10. BYTE bGetReceive;  
  11. DWORD PID;  
  12. HANDLE hProcess;  
  13. HANDLE hThread;  
  14. INT iflag = 1;  
  15. INT iRunTime = 1;  
  16. std::map<DWORDHANDLE> m_thread;  
  17. std::map<DWORD,BYTE> m_code;  
  18. std::map<DWORD,LPVOID> m_singlethread;  
  19. std::queue<INT> s_breakpoint;  
  20. typedef std::queue<LPSTR>  FUNC_PARAM;   
  21. std::map<std::string,std::string> m_autoprintf;  
  22. std::queue<CHAR> q_string;   
  23.  
  24. typedef struct FUNCINFO{  
  25.     LPSTR lpApiName;  
  26.     FUNC_PARAM fpParamList;  
  27.     LPSTR lpReturnValue;  
  28. } FUNCINFO,*LPFUNCINFO; //定义函数信息结构体 

 

 MonApi.cpp

 
  1. #include <Windows.h>  
  2. #include <stdio.h>  
  3. #include <iostream>  
  4. #include <string>  
  5. #include <TlHelp32.h>  
  6. #include <ImageHlp.h>  
  7. #include <Psapi.h>  
  8. #include <map>  
  9. #include <queue>  
  10. #include <stack>  
  11. #include "header.h"  
  12.  
  13. using namespace std;  
  14.  
  15. #define STRING_LENGTH 512  
  16. #define BUFSIZE 512  
  17. #define PARAM_SIZE 10  
  18.  
  19. #pragma comment(lib,"psapi.lib")  
  20.  
  21.  
  22.  
  23. ///////////////////赋予进程DEBUG权限Done///////////////////  
  24. string DealString(HANDLE hTarProcess,DWORD dwStringAddr);  
  25.  
  26. BOOL ImprovePriv()  
  27. {  
  28.     HANDLE hToken;  
  29.     if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))  
  30.     {  
  31.         printf("\n打开进程失败.(%d)",GetLastError());  
  32.         return FALSE;  
  33.     }  
  34.     TOKEN_PRIVILEGES tkp;  
  35.     tkp.PrivilegeCount = 1;  
  36.     if(!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid))  
  37.     { //查看指定权限对应的LUID  
  38.         printf("\n查看进程权限信息失败.(%d)",GetLastError());  
  39.         return FALSE;  
  40.     }  
  41.     tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  
  42.     if(!AdjustTokenPrivileges(hToken,FALSE,&tkp,0,NULL,NULL))  
  43.     { //调整令牌权限  
  44.         printf("\n调整令牌权限失败.(%d)",GetLastError());  
  45.         return FALSE;  
  46.     }  
  47.     CloseHandle(hToken);  
  48.     CloseHandle(hProcess);  
  49.     return TRUE;  
  50. }  
  51. /////////////////////////////////////////////////////////////////////  
  52. ////////////////////////////////动态输出函数参数及返回值///////////////////////////////  
  53. BOOL AutoPrint(HANDLE hMyProcess,LPCONTEXT pCt,FUNCINFO * pApiName,INT isReturn)  
  54. {  
  55.     FUNCINFO ApiInfo = *pApiName;  
  56.     LPTSTR lpFormatString;  
  57.     lpFormatString = (LPSTR)HeapAlloc(GetProcessHeap(),0,BUFSIZE);  
  58.     ZeroMemory(lpFormatString,MAX_PATH);  
  59.     if(!isReturn){  
  60.     LPSTR lpPreFormat = "\nParam %d[%s]:";   
  61.     INT paramSize =ApiInfo.fpParamList.size();  
  62.     LPBYTE callMem = NULL;  
  63.     DWORD dwHasRead;  
  64.     int nMemLen = 4*paramSize;   
  65.     callMem = (LPBYTE)malloc(nMemLen);  
  66.     ReadProcessMemory(hMyProcess, (void*)(pCt->Esp + 4), callMem, nMemLen, &dwHasRead);  
  67.     DWORD* pMem = (DWORD*)callMem;  
  68.     for(int index = 0;index<paramSize;index++)  
  69.     {  
  70.         LPSTR paramTemp = (LPSTR)m_autoprintf[ApiInfo.fpParamList.front()].data();  
  71.         if(strcmp(paramTemp,"%s") == 0)  
  72.         {  
  73.             string TempStr=  DealString(hMyProcess,*(pMem+index));  
  74.             LPSTR lpGetString = (LPSTR)TempStr.data();  
  75.             printf("\nParam %d[%s]:%s",index+1,ApiInfo.fpParamList.front(),lpGetString);  
  76.         }else 
  77.         {  
  78.         wsprintf(lpFormatString,paramTemp,*(pMem+index));  
  79.         printf("\nParam %d[%s]:%s",index+1,ApiInfo.fpParamList.front(),lpFormatString);  
  80.         }  
  81.         ApiInfo.fpParamList.pop();  
  82.     }  
  83.  
  84.     }else 
  85.     {  
  86.         DWORD dwRc = pCt->Eax;  
  87.         LPSTR returnTemp = (LPSTR)m_autoprintf[ApiInfo.lpReturnValue].data();  
  88.         if(strcmp(returnTemp,"%s") == 0)  
  89.         {  
  90.             string TempStr  = DealString(hMyProcess,dwRc);  
  91.             LPSTR lpGetString = (LPSTR)TempStr.data();  
  92.             printf("\nReturn param[%s]:%s",ApiInfo.lpReturnValue,lpGetString);  
  93.         }else 
  94.         {  
  95.             wsprintf(lpFormatString,returnTemp,dwRc);  
  96.             printf("\nReturn param[%s]:%s",ApiInfo.lpReturnValue,lpFormatString);  
  97.         }  
  98.     }  
  99.     return TRUE;  
  100. }  
  101. ////////////////////////////////////////////////////////////////////////////////////////////////////////  
  102. ///////////////////////////////处理变长字符串/////////////////////////////////////  
  103. string DealString(HANDLE hTarProcess,DWORD dwStringAddr)  
  104. {  
  105.         DWORD dwHasRead;  
  106.         INT iIndex = 0;  
  107.         INT iCount = 0;  
  108.         CHAR  ctemp;  
  109.         do{  
  110.             ReadProcessMemory(hTarProcess,(PVOID)(dwStringAddr+iIndex),&ctemp,sizeof(BYTE),&dwHasRead);  
  111.             q_string.push(ctemp);  
  112.             iIndex++;  
  113.         }while((ctemp)!= '\0');  
  114.         string sfinal;  
  115.         int len = q_string.size();  
  116.         while(iCount < len - 1)  
  117.         {  
  118.             sfinal += q_string.front();  
  119.             q_string.pop();  
  120.             iCount++;  
  121.         }  
  122.         q_string.pop(); //弹出最后的截断字符  
  123.     return sfinal;  
  124.       
  125.     //return lpVarLenString;  
  126. }  
  127. //////////////////////////////////////////////////////////////////////////////////////////  
  128.  
  129. ////////////////////////////初始化映射表Done//////////////////////////////////  
  130. VOID InitMap(){  
  131.     m_autoprintf.insert(make_pair("HWND","0x%08X"));  
  132.     m_autoprintf.insert(make_pair("LPCSTR","%s"));  
  133.     m_autoprintf.insert(make_pair("UINT","0x%08X"));  
  134.     m_autoprintf.insert(make_pair("INT","0x%08X"));  
  135. }  
  136. //////////////////////////////////////////////////////////////////////////////////////////  
  137.  
  138. ///////////////////断点处理过程Done/////////////////////  
  139.  BYTE SetBreakPoint(DWORD pAddr,BYTE code){  
  140.     BOOL bResult = TRUE;  
  141.     DWORD dwHasRead,dwOldFlag;  
  142.  
  143.     if(pAddr >= 0x80000000 || pAddr == 0){  
  144.         printf("\n函数地址非法.(%d)",GetLastError());  
  145.         CloseHandle(hProcess);  
  146.         return code;  
  147.     }  
  148.  
  149.     bResult = ReadProcessMemory(hProcess,(LPVOID)pAddr,&m_code[pAddr],sizeof(BYTE),&dwHasRead);//读出函数入口出的原始代码  
  150.     if(bResult == 0){  
  151.         printf("\n读取内存失败.(%d)",GetLastError());  
  152.         CloseHandle(hProcess);  
  153.         return code;  
  154.     }  
  155.     VirtualProtectEx(hProcess,(LPVOID)pAddr,sizeof(UCHAR),PAGE_READWRITE,&dwOldFlag);   //更改内存区域保护属性  
  156.     WriteProcessMemory(hProcess,(LPVOID)pAddr,&code,sizeof(UCHAR),&dwHasRead); //向进程内存中写入数据  
  157.     VirtualProtectEx(hProcess,(LPVOID)pAddr,sizeof(UCHAR),dwOldFlag,&dwOldFlag); //将内存保护属性改回初始状态  
  158.     return m_code[pAddr];  
  159. }  
  160. ///////////////////////////////////////////////////  
  161.  
  162. ////////////////////中断处理过程///////////////////  
  163.  
  164.  
  165.  
  166. BOOL WINAPI DealDebugException(DEBUG_EVENT *pEvent,FUNCINFO * pApiInfo){  
  167.     DWORD dwHasRead;  
  168.     LPVOID lpBreakAddr = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;  
  169.     CONTEXT ct;  
  170.     DWORD code = pEvent->u.Exception.ExceptionRecord.ExceptionCode;  
  171.     HANDLE hMyThread = m_thread[pEvent->dwThreadId];  
  172.     switch (code)  
  173.     {  
  174.     case STATUS_BREAKPOINT:  
  175.         SuspendThread(hMyThread);  
  176.         ct.ContextFlags = CONTEXT_FULL;  
  177.         GetThreadContext(hMyThread,&ct);  
  178.         ResumeThread(hMyThread);  
  179.         if(iRunTime == 1){  
  180.             s_breakpoint.push(1);  
  181.         }  
  182.             if (s_breakpoint.front() == 1)  
  183.             {  
  184.                 if((DWORD)lpBreakAddr < 0x80000000){  
  185.                     printf("\n\n::[API]:%s",pApiInfo->lpApiName);  
  186.                     printf("\n::[MSG]BreakPoint at 0x%08X.",lpBreakAddr);   
  187.                     AutoPrint(hProcess,&ct,pApiInfo,0);  
  188.                     SuspendThread(hThread);  
  189.                     ct.Eip--;  
  190.                     ct.EFlags |= 0x00100;  
  191.                     m_singlethread[pEvent->dwThreadId] = lpBreakAddr;  
  192.                     SetBreakPoint((DWORD)lpBreakAddr,m_code[(DWORD)lpBreakAddr]);  
  193.                     SetThreadContext(hMyThread,&ct);  
  194.                     ResumeThread(hThread);  
  195.  
  196.                     void * lpCallBackAddr;  
  197.                     DWORD buf;  
  198.                     ReadProcessMemory(hProcess,(void*)ct.Esp,&buf,sizeof(buf),&dwHasRead);  
  199.                     lpCallBackAddr = (void *)buf;  
  200.                     if((DWORD)lpCallBackAddr < 0x80000000)  
  201.                     {  
  202.  
  203.                         m_code[(DWORD)lpCallBackAddr] = SetBreakPoint((DWORD)lpCallBackAddr,0xCC);   
  204.                     }  
  205.                 }  
  206.                 s_breakpoint.push(2);  
  207. }  
  208.  
  209.             ///////////////////////////////////////////两次中断分割///////////////////////////////////  
  210.  
  211.             if (s_breakpoint.front() == 2)  
  212.             {  
  213.                         DWORD dwRc = ct.Eax;  
  214.                         printf("\n::[MSG]CallBackAddr:%p",lpBreakAddr);  
  215.                         AutoPrint(hProcess,&ct,pApiInfo,1);  
  216.                         SuspendThread(hMyThread);  
  217.                         ct.Eip--;  
  218.                         SetBreakPoint((DWORD)lpBreakAddr,m_code[(DWORD)lpBreakAddr]);  
  219.                         SetThreadContext(hMyThread, &ct);  
  220.                         m_singlethread[pEvent->dwThreadId] = lpBreakAddr;  
  221.                         ResumeThread(hMyThread);  
  222.                         s_breakpoint.push(1);  
  223. }  
  224.         s_breakpoint.pop();  
  225.         iRunTime++;  
  226.         break;  
  227.     case EXCEPTION_SINGLE_STEP:  
  228.         SetBreakPoint((DWORD)m_singlethread[pEvent->dwThreadId],0xCC);  
  229.         m_singlethread.erase(pEvent->dwThreadId);  
  230.         break;  
  231.     default:  
  232.         return FALSE;  
  233.     }  
  234.     return TRUE;  
  235. }  
  236. ///////////////////////////////////////////////////////////////////////////////  
  237.  
  238. /////////////////////获得被调试程序模块列表Done////////////////////  
  239. BOOL GetFileNameFromHandle(HANDLE hFile)   
  240. {  
  241.     BOOL bSuccess = FALSE;  
  242.     TCHAR pszFilename[MAX_PATH+1];  
  243.     HANDLE hFileMap;  
  244.  
  245.     LPSTR strFilename;  
  246.     strFilename = (LPSTR) HeapAlloc(GetProcessHeap(),0,MAX_PATH);  
  247.     ZeroMemory(strFilename,MAX_PATH);  
  248.  
  249.     DWORD dwFileSizeHi = 0;  
  250.     DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi);   
  251.       
  252.     if( dwFileSizeLo == 0 && dwFileSizeHi == 0 )  
  253.     {       
  254.         return FALSE;  
  255.     }  
  256.  
  257.  
  258.     hFileMap = CreateFileMapping(hFile,   
  259.         NULL,   
  260.         PAGE_READONLY,  
  261.         0,   
  262.         1,  
  263.         NULL);  
  264.  
  265.     if (hFileMap)   
  266.     {  
  267.           
  268.         void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0,1);  
  269.  
  270.         if (pMem)   
  271.         {  
  272.             if (GetMappedFileName (GetCurrentProcess(),   
  273.                 pMem,   
  274.                 pszFilename,  
  275.                 MAX_PATH))   
  276.             {  
  277.  
  278.                   
  279.                 TCHAR szTemp[BUFSIZE];  
  280.                 szTemp[0] = '\0';  
  281.  
  282.                 if (GetLogicalDriveStrings(BUFSIZE-1, szTemp))   
  283.                 {  
  284.                     TCHAR szName[MAX_PATH];  
  285.                     TCHAR szDrive[3] = TEXT(" :");  
  286.                     BOOL bFound = FALSE;  
  287.                     TCHAR* p = szTemp;  
  288.  
  289.                     do   
  290.                     {  
  291.                           
  292.                         *szDrive = *p;  
  293.  
  294.           
  295.                         if (QueryDosDevice(szDrive, szName, MAX_PATH))  
  296.                         {  
  297.                             size_t uNameLen = lstrlen(szName);  
  298.  
  299.                             if (uNameLen < MAX_PATH)   
  300.                             {  
  301.                                 bFound = strnicmp(pszFilename, szName,   
  302.                                     uNameLen) == 0;  
  303.  
  304.                                 if (bFound)   
  305.                                 {  
  306.                                     wsprintf(strFilename,"%s%s",szDrive, pszFilename+uNameLen);  
  307.                                     printf("\n%s",strFilename);  
  308.                                 }  
  309.                             }  
  310.                         }  
  311.  
  312.  
  313.                         while (*p++);  
  314.                     } while (!bFound && *p);   
  315.                 }  
  316.             }  
  317.             bSuccess = TRUE;  
  318.             UnmapViewOfFile(pMem);  
  319.         }   
  320.  
  321.         CloseHandle(hFileMap);  
  322.     }  
  323.     return TRUE;  
  324. }  
  325. ///////////////////////////////////////////////////////////////  
  326.  
  327. ///////////////////调试监视处理过程Done/////////////////////////////  
  328. BOOL WINAPI DealDebugEvent(DEBUG_EVENT * pEvent,FUNCINFO * pApiInfo){  
  329.     BOOL bResult = TRUE;  
  330.     switch (pEvent->dwDebugEventCode)  
  331.     {  
  332.     case CREATE_PROCESS_DEBUG_EVENT:  
  333.         hProcess = pEvent->u.CreateProcessInfo.hProcess;  
  334.         m_thread[pEvent->dwThreadId] = pEvent->u.CreateProcessInfo.hThread;  
  335.         break;  
  336.     case CREATE_THREAD_DEBUG_EVENT:  
  337.         m_thread[pEvent->dwThreadId] = pEvent->u.CreateProcessInfo.hThread;  
  338.         break;  
  339.     case EXIT_THREAD_DEBUG_EVENT:  
  340.         break;  
  341.     case EXIT_PROCESS_DEBUG_EVENT:  
  342.         break;  
  343.     case EXCEPTION_DEBUG_EVENT:  
  344.         if(iflag > 1){  
  345.         bResult = DealDebugException(pEvent,pApiInfo);  
  346.         }  
  347.         iflag++;  
  348.         break;  
  349.     case LOAD_DLL_DEBUG_EVENT:  
  350.         bResult = GetFileNameFromHandle(pEvent->u.LoadDll.hFile);  
  351.         break;  
  352.     default:  
  353.         break;  
  354.     }  
  355.     return bResult;  
  356. }  
  357.  
  358. /////////////////////////////////////////////////////////////  
  359.  
  360. //////////////////////设置断点线程Done//////////////////////////  
  361. DWORD WINAPI SetBreakProc(LPVOID lpParam){  
  362.     DWORD dwFuncAddress;  
  363.     Sleep(1000);  
  364.     dwFuncAddress =(DWORD) GetProcAddress(LoadLibrary("User32.dll"),"MessageBoxA");  
  365.     m_code[dwFuncAddress] = SetBreakPoint(dwFuncAddress,0xCC); //在函数入口点处设置断点  
  366.  
  367.     if(bGetReceive == 0xCC){  
  368.         printf("\n设置断点失败.(%d)",GetLastError());  
  369.         return 0;  
  370.     }else{  
  371.         printf("\n设置断点成功.");  
  372.     }  
  373.     return 1;  
  374. }  
  375. ////////////////////////////////////////////////////////////  
  376.  
  377. /////////////////////调试器处理线程Done/////////////////////////  
  378. DWORD WINAPI DebugProc(LPVOID lpParam){  
  379.     if(ImprovePriv()){  
  380.         printf("提权成功.\n");  
  381.     }  
  382.     //////////////////////////////////挂载运行时进程将此段注释掉/////////////////////////////////////  
  383.     LPSTR lpCmdLine = "WaitMessageBox.exe";  
  384.     DEBUG_EVENT dbe;  
  385.     BOOL bResult = TRUE;  
  386.     STARTUPINFO si;  
  387.     PROCESS_INFORMATION pi;  
  388.     ZeroMemory(&si,sizeof(si));  
  389.     ZeroMemory(&pi,sizeof(pi));  
  390.     si.cb = sizeof(si);  
  391.     if(!CreateProcess(NULL, (LPTSTR)(LPCTSTR)lpCmdLine, NULL, NULL, FALSE,       
  392.         DEBUG_ONLY_THIS_PROCESS,  
  393.         NULL, NULL, &si, &pi)){  
  394.             printf("\n启动被调试程序失败.(%d)",GetLastError());  
  395.             return 0;  
  396.     }  
  397.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
  398.     /*DebugActiveProcess(6032);*/  //先启动WaitMessageBox将PID值更新并取消注释  
  399.     while (WaitForDebugEvent(&dbe,INFINITE)) //循环等待Debug事件结束  
  400.     {  
  401.         if(dbe.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)  
  402.         {  
  403.             break;    
  404.         }  
  405.         bResult = DealDebugEvent(&dbe,(FUNCINFO *)lpParam);  
  406.  
  407.         if(bResult){  
  408.             ContinueDebugEvent(dbe.dwProcessId,dbe.dwThreadId,DBG_CONTINUE);  //若Debug事件成功处理则继续执行  
  409.         }else{  
  410.             ContinueDebugEvent(dbe.dwProcessId,dbe.dwThreadId,DBG_EXCEPTION_NOT_HANDLED);   
  411.         }  
  412.     }  
  413.  
  414. //  CloseHandle(pi.hThread);  
  415. //  CloseHandle(pi.hProcess);  
  416.     return 1;  
  417. }  
  418. ///////////////////////////////////////////////////////////  
  419. int main(int argc,char * argv[]){  
  420.     DWORD dwTid;  
  421.     DWORD dwNewTid;  
  422.     HANDLE hNewThread[2];  
  423.     FUNCINFO  fpInfo;  
  424.     //初始化函数信息结构体  
  425.     fpInfo.lpApiName = "MessageBoxA";  
  426.     fpInfo.fpParamList.push("HWND");  
  427.     fpInfo.fpParamList.push("LPCSTR");  
  428.     fpInfo.fpParamList.push("LPCSTR");  
  429.     fpInfo.fpParamList.push("UINT");  
  430.     fpInfo.lpReturnValue = "INT";  
  431.     InitMap(); //初始化字符映射表  
  432.     //启动调试线程和断点设置线程  
  433.     hNewThread[0] = CreateThread(NULL,0,DebugProc,(LPVOID)&fpInfo,NULL,&dwTid);  
  434.     if(hNewThread[0] == NULL){  
  435.         printf("\n启动调试线程失败.(%d)",GetLastError());  
  436.         return 0;  
  437.     }  
  438.     hNewThread[1] = CreateThread(NULL,0,SetBreakProc,NULL,NULL,&dwNewTid);  
  439.  
  440.  
  441.     WaitForMultipleObjects(2,hNewThread,TRUE,INFINITE);  
  442.     CloseHandle(hNewThread[0]);  
  443.     CloseHandle(hNewThread[1]);  
  444.     return 1;  

 















本文转hackfreer51CTO博客,原文链接:http://blog.51cto.com/pnig0s1992/782233,如需转载请自行联系原作者

相关文章
|
JSON 移动开发 Java
氚云丨开发课— 09平台 API 的调用及自定义 API 的编写与调用| 学习笔记
快速学习氚云丨开发课— 09平台 API 的调用及自定义 API 的编写与调用。
908 0
|
JSON 数据可视化 API
PhalApi 2.x 开发文档 运行Hello World 编写第一个API接口
此文章假设你已成功安装PhalApi2项目,如果尚未安装,可阅读下载与安装。
|
存储 前端开发 JavaScript
Python3,网站搭建之编写API接口,让前端页面展示数据!
Python3,网站搭建之编写API接口,让前端页面展示数据!
562 0
Python3,网站搭建之编写API接口,让前端页面展示数据!
|
SQL Linux 测试技术
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API(下)
本文将使用功能强大的 Gorilla Mux、GORM 和 CockroachDB 编写可维护 RESTful API。
|
关系型数据库 数据库连接 API
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API(中)
本文将使用功能强大的 Gorilla Mux、GORM 和 CockroachDB 编写可维护 RESTful API。
|
SQL Cloud Native 关系型数据库
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API(上)
本文将使用功能强大的 Gorilla Mux、GORM 和 CockroachDB 编写可维护 RESTful API。
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API(上)
|
XML JSON Kubernetes
在 Node.js 中使用 Yaml 编写API文档
在文章《使用Node.js、MongoDB、Fastify 构建API服务》中介绍使用 Swagger 构建 API 文档,编写文档不是那么的顺手,本文介绍另一种编写 API 文档的方式,即使用 Yaml ,将API文档与其实现完全分开。
309 0
在 Node.js 中使用 Yaml 编写API文档
|
JavaScript 前端开发 测试技术
Chai 和 Mocha 为API编写测试
昨天在文章《JavaScript单元测试的“抹茶”组合:Mocha和Chai》介绍了JavaScript单元测试常用组合,本文展示使用 Chai 和 Mocha 为API编写简单的测试用例。
196 0
Chai 和 Mocha 为API编写测试
|
API 数据库 Python
|
XML IDE Java
【Groovy】Gradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )
【Groovy】Gradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )
239 0
【Groovy】Gradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )