ïŒ2018幎çïŒ
ãã²ã«ã»ã°ãªã³ããŒã°
ããã¯ãMega-Tutorialã®23çªç®ã®éšåã§ããã¢ããªã±ãŒã·ã§ã³ããã°ã©ãã³ã°ã€ã³ã¿ãŒãã§ã€ã¹ïŒãŸãã¯APIïŒã䜿çšããŠããã€ã¯ãããã°ãæ¡åŒµããæ¹æ³ã説æããŸãã
ãã¿ãã¬ã®äžã«ã¯ã2018幎ã·ãªãŒãºã®ãã¹ãŠã®èšäºã®ãªã¹ãããããŸãã
- 第1ç« ïŒHello worldïŒ
- 第2ç« ïŒãã³ãã¬ãŒã
- 第3ç« ïŒWebãã©ãŒã
- 第4ç« ïŒããŒã¿ããŒã¹
- 第5ç« ïŒãŠãŒã¶ãŒãã°ã€ã³
- 第6ç« ïŒãããã£ãŒã«ããŒãžãšã¢ãã¿ãŒ
- 第7ç« ïŒãšã©ãŒåŠç
- 第8ç« ïŒãµãã¹ã¯ã©ã€ããŒãé£çµ¡å ãããã³ãã¬ã³ã
- 第9ç« ïŒããŒãžããŒã·ã§ã³
- 第10ç« ïŒã¡ãŒã«ãµããŒã
- 第11ç« ïŒåæ§æ
- 第12ç« ïŒæ¥ä»ãšæå»
- 第13ç« ïŒI18nããã³L10n
- 第14ç« ïŒAjax
- 第15ç« ïŒã¢ããªã±ãŒã·ã§ã³æ§é ã®æ¹å
- 第16ç« ïŒå šææ€çŽ¢
- 第17ç« ïŒLinuxã®å±é
- 第18ç« ïŒHerokuã§ã®å±é
- 第19ç« ïŒDockerã³ã³ãããŒã§ã®å±é
- 第20ç« ïŒJavaScriptããžãã¯
- 第21ç« ïŒãŠãŒã¶ãŒéç¥
- 第22ç« ïŒããã¯ã°ã©ãŠã³ãã¿ã¹ã¯
- 第23ç« ïŒã¢ããªã±ãŒã·ã§ã³ããã°ã©ãã³ã°ã€ã³ã¿ãŒãã§ã€ã¹ïŒAPIïŒ ïŒãã®èšäºïŒ
泚1ïŒãã®ã³ãŒã¹ã®å€ãããŒãžã§ã³ããæ¢ãã®å Žåã¯ããã¡ããã芧ãã ãã ã
ãã®ã¢ããªã±ãŒã·ã§ã³çšã«ãããŸã§ã«äœæãããã¹ãŠã®æ©èœã¯ãWebãã©ãŠã¶ãšããç¹å®ã®çš®é¡ã®ã¯ã©ã€ã¢ã³ãåãã«èšèšãããŠããŸãã ããããä»ã®ã¿ã€ãã®é¡§å®¢ã¯ã©ãã§ããããïŒ ããšãã°ãAndroidãŸãã¯iOSçšã®ã¢ããªã±ãŒã·ã§ã³ãäœæããå Žåãäž»ã«2ã€ã®è§£æ±ºæ¹æ³ããããŸãã æãç°¡åãªè§£æ±ºçã¯ãç»é¢å šäœãåããŠMicroblog Webãµã€ããããŒãããWebã³ã³ããŒãã³ãã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ãäœæããããšã§ãããããã¯ããã€ã¹ã®Webãã©ãŠã¶ãŒã§ã¢ããªã±ãŒã·ã§ã³ãéãããšãã質çã«ã¯è¯ããããŸããã ããåªãããœãªã¥ãŒã·ã§ã³ïŒã¯ããã«æéãããããŸãïŒã¯ç¬èªã®ã¢ããªã±ãŒã·ã§ã³ãäœæããããšã§ããããã®ã¢ããªã±ãŒã·ã§ã³ã¯HTMLããŒãžã®ã¿ãè¿ããµãŒããŒãšã©ã®ããã«ããåãã§ããŸããïŒ
ããã¯ã ã¢ããªã±ãŒã·ã§ã³ããã°ã©ãã³ã°ã€ã³ã¿ãŒãã§ã€ã¹ ïŒãŸãã¯APIïŒã圹ç«ã€åé¡é åã§ãã APIã¯ãã¢ããªã±ãŒã·ã§ã³ãžã®äœã¬ãã«ã®ãšã³ããªãã€ã³ããšããŠèšèšãããHTTPã«ãŒãã®ã³ã¬ã¯ã·ã§ã³ã§ãã ã«ãŒããå®çŸ©ããWebãã©ãŠã¶ãŒã§äœ¿çšãããHTMLãè¿ãé¢æ°ã衚瀺ãã代ããã«ãAPIã䜿çšãããšãã¯ã©ã€ã¢ã³ãã¯ã¢ããªã±ãŒã·ã§ã³ãªãœãŒã¹ãçŽæ¥æäœããŠããŠãŒã¶ãŒã«æ å ±ãå®å šã«ã¯ã©ã€ã¢ã³ãã«æ瀺ããæ¹æ³ã決å®ã§ããŸãã ããšãã°ããã€ã¯ãããã°ã®APIã¯ãã¯ã©ã€ã¢ã³ãã«ãŠãŒã¶ãŒæ å ±ãšããã°ãšã³ããªãæäŸãããŠãŒã¶ãŒãæ¢åã®ããã°ãšã³ããªãç·šéã§ããããã«ããŸããããã®ããžãã¯ãHTMLãšæ··åããããšãªããããŒã¿ã¬ãã«ã§ã®ã¿å¯èœã§ãã
ã¢ããªã±ãŒã·ã§ã³ã§çŸåšå®çŸ©ãããŠãããã¹ãŠã®ã«ãŒãã調ã¹ããšãäžèšã§äœ¿çšããAPIå®çŸ©ã«äžèŽãããã®ãããã€ãããããšã«æ°ä»ãã§ãããã ããããèŠã€ããŸãããïŒ ç¬¬14ç« ã§å®çŸ©ãããŠãã/ translateã«ãŒããªã©ãJSONãè¿ãããã€ãã®ã«ãŒãã«ã€ããŠèª¬æããŠããŸãã ããã¯ã POST
ãªã¯ãšã¹ãã§ããã¹ãããœãŒã¹èšèªãããã³å®å
èšèªããã¹ãŠã®ããŒã¿ãJSON圢åŒã§åãå
¥ããã«ãŒãã§ãã ãã®èŠæ±ã«å¯Ÿããå¿çã¯ããã®ããã¹ãã®ç¿»èš³ã§ãããJSON圢åŒã§ããããŸãã ãµãŒããŒã¯èŠæ±ãããæ
å ±ã®ã¿ãè¿ããã¯ã©ã€ã¢ã³ãã«ã¯ãã®æ
å ±ããŠãŒã¶ãŒã«æ瀺ãã責任ããããŸãã
ã¢ããªã±ãŒã·ã§ã³ã®JSONã«ãŒãã«ã¯ããã£ãŒã«ãAPIããããŸããããã©ãŠã¶ãŒã§å®è¡ãããWebã¢ããªã±ãŒã·ã§ã³ããµããŒãããããã«èšèšãããŠããŸãã
ã¢ããªã±ãŒã·ã§ã³å
ã®JSONã«ãŒãã«ã¯APIããããŸããããæèŠãã¯ããã©ãŠã¶ãŒã§å®è¡ãããWebã¢ããªã±ãŒã·ã§ã³ããµããŒãããããã«èšèšãããŠããããšã§ãã ã¹ããŒããã©ã³ã¢ããªã±ãŒã·ã§ã³ããããã®ã«ãŒãã䜿çšãããå Žåãç»é²ããããŠãŒã¶ãŒãå¿
èŠã§ããããã°ã€ã³ã¯HTMLãã©ãŒã ãä»ããŠã®ã¿å¯èœã§ããããã䜿çšã§ããªãããšã«æ³šæããŠãã ããã ãã®ç« ã§ã¯ãWebãã©ãŠã¶ãŒã«äŸåããªãAPIãäœæããæ¹æ³ã説æããã©ã®ã¯ã©ã€ã¢ã³ãããããã«æ¥ç¶ããããä»®å®ããŸããã
ãã®ç« ã®GitHubãªã³ã¯ïŒ Browse ã Zip ã Diff ã
APIèšèšã®åºç€ãšããŠã®REST
誰ããäžèšã®ç§ã®å£°æã«åŒ·ãå察ãããããããŸãã/ãã®ç¿»èš³ãšä»ã®JSONã«ãŒãã¯APIã«ãŒãã§ãã ä»ã®äººã¯ãããããäžååã«èšèšãããAPIã§ãããšèãããšããèŠåã«åæãããããããŸããã ã§ã¯ãé©åã«èšèšãããAPIã®ç¹åŸŽã¯äœã§ããïŒãŸããJSONã«ãŒãããã®ã«ããŽãªå€ã«ããã®ã¯ãªãã§ããïŒ
REST APIãšããçšèªãèããããšããããããããŸããã RESTã¯ãRepresentational State Transferã®ç¥ã§ãRoy Fieldingå士ãå士è«æã§ææ¡ããã¢ãŒããã¯ãã£ã§ãã ãã£ãŒã«ãã£ã³ã°å士ã¯ãRESTã®6ã€ã®ç¹åŸŽãããªãæœè±¡çãã€äžè¬çãªæ¹æ³ã§ç€ºããŠããŸãã
ãã£ãŒã«ãã£ã³ã°å士ã®è«æãšã¯å¥ã«ãä»ã®ä¿¡é Œã§ããRESTä»æ§ã¯ãªããèªè ã«èªç±ã«è§£éãããäœå°ããããŸãã äžããããREST APIãäžè²«ããŠãããã©ããã®ãããã¯ã¯ãREST APIã6ã€ã®ãã¹ãŠã®ç¹æ§ãé å®ããRESTã®ããã©ã°ããã£ã¹ãããšæ¯èŒããŠæ確ã«è¡ãå¿ èŠããããšä¿¡ããRESTãçŽç²äž»çŸ©è ãã®éã®æ¿ããè°è«ã®æºã§ãããã£ãŒã«ãã£ã³ã°å士ã®è«æã§ã¬ã€ãã©ã€ã³ãŸãã¯æšå¥šäºé ãšããŠæ瀺ãããã¢ã€ãã¢ã ãã£ãŒã«ãã£ã³ã°å士èªèº«ãçŽç²äž»çŸ©è é£å¶ã®å³æ¹ã§ãããããã°ãšãªã³ã©ã€ã³ã®è§£èª¬ã«ã€ããŠã®åœŒã®ããžã§ã³ã«ããã€ãã®è¿œå ã®æŽå¯ãäžããŸããã
çŸåšå®è£ ãããŠããAPIã®å€§éšåã¯ããå®çšçãªãRESTå®è£ ã«æºæ ããŠããŸãã ããã«ã¯ãFacebookãGitHubãTwitterãªã©ã®ãããã°ãã¬ãŒã€ãŒãã®ã»ãšãã©ã®APIãå«ãŸããŸãã ã»ãšãã©ã®APIã¯çŽç²äž»çŸ©è ãå¿ é ãšèããå®è£ ã®è©³çŽ°ãã¹ããããããããçŽç²ãªRESTãšæºå ŽäžèŽã§èãããããããªãã¯APIã¯ã»ãšãã©ãããŸããã Dr. Fieldingãä»ã®RESTã®çŽç²äž»çŸ©è ã¯REST APIãããã§ãããããã§ãªããšããå³æ ŒãªèŠè§£ã«ããããããããœãããŠã§ã¢æ¥çã¯éåžžãå®çšçãªæå³ã§RESTã«èšåããŠããŸãã
RESTè«æã®å 容ãç解ããããã«ã以äžã®ã»ã¯ã·ã§ã³ã§ã¯ããã£ãŒã«ãã£ã³ã°å士ããªã¹ããã6ã€ã®ååã«ã€ããŠèª¬æããŸãã
ã¯ã©ã€ã¢ã³ããµãŒããŒ
ã¯ã©ã€ã¢ã³ããšãµãŒããŒã®ååã¯éåžžã«åçŽã§ããRESTAPIã§ã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒã®åœ¹å²ãæ確ã«åºå¥ããå¿ èŠãããããšã瀺ããŠããã ãã§ãã å®éã«ã¯ãããã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒããã©ã³ã¹ããŒããä»ããŠçžäºäœçšããå¥åã®ããã»ã¹ã«ããããšãæå³ããŸããã»ãšãã©ã®å Žåãããã¯TCPãããã¯ãŒã¯äžã®HTTPãããã³ã«ã§ãã
éå±€åã·ã¹ãã
éå±€åã·ã¹ãã ïŒ ãã«ãã¬ãã«ã·ã¹ãã ïŒã®åçã§ã¯ãã¯ã©ã€ã¢ã³ãããµãŒããŒãšå¯Ÿè©±ããå¿ èŠãããå Žåãå®éã®ãµãŒããŒã§ã¯ãªãã仲ä»è ã«æ¥ç¶ã§ããŸãã ã¢ã€ãã¢ã¯ãã¯ã©ã€ã¢ã³ãããµãŒããŒã«çŽæ¥æ¥ç¶ãããŠããªãå Žåãã¯ã©ã€ã¢ã³ããèŠæ±ãéä¿¡ããæ¹æ³ã«ãŸã£ããéãã¯ãªããšããããšã§ããå®éãã¿ãŒã²ãããµãŒããŒã«æ¥ç¶ãããŠãããã©ããããããããªãå ŽåããããŸãã åæ§ã«ããã®ååã§ã¯ããµãŒããŒã¯ã¯ã©ã€ã¢ã³ãããçŽæ¥ã§ã¯ãªãã仲ä»è ããã¯ã©ã€ã¢ã³ãèŠæ±ãåä¿¡ã§ãããããæ¥ç¶ã®å察åŽãã¯ã©ã€ã¢ã³ãã§ãããšæ³å®ããŠã¯ãªããŸããã
ããã¯éèŠãªRESTæ©èœã§ããäžéããŒããè¿œå ã§ãããããã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã¯ããŒããã©ã³ãµãŒããã£ãã·ã¥ããããã·ãªã©ã䜿çšããŠå€§éã®èŠæ±ãæºããããšãã§ãã倧èŠæš¡ã§è€éãªãããã¯ãŒã¯ãéçºã§ããŸãã
ãã£ãã·ã¥
ãã®ååã¯ããµãŒããŒãŸãã¯ãããŒã«ãŒãã·ã¹ãã ããã©ãŒãã³ã¹ãåäžãããããã«é »ç¹ã«åä¿¡ãããèŠæ±ã«å¯Ÿããå¿çããã£ãã·ã¥ã§ããããšãæ瀺çã«ç€ºãããšã«ãããéå±€åã·ã¹ãã ãæ¡åŒµããŸãã ããããããªãã¿ã®ãã£ãã·ã¥å®è£ ããããŸãããã¹ãŠã®Webãã©ãŠã¶ãŒã«1ã€ãããŸãã Webãã©ãŠã¶ãŒã®ãã£ãã·ã¥ã¬ã€ã€ãŒã¯ãç»åãªã©ã®åããã¡ã€ã«ãäœåºŠãèŠæ±ããããšãé¿ããããã«ãã䜿çšãããŸãã
APIã®ç®çã®ããã«ãã¿ãŒã²ãããµãŒããŒã¯ããã£ãã·ã¥å¶åŸ¡ã®å©ããåããŠãå¿çãã¯ã©ã€ã¢ã³ãã«è¿ããããšãã«ä»²ä»è ãå¿çããã£ãã·ã¥ã§ãããã©ããã瀺ãå¿ èŠããããŸãã ã»ãã¥ãªãã£äžã®çç±ãããå®çšŒåç°å¢ã«ãããã€ãããAPIã¯æå·åã䜿çšããå¿ èŠããããããéåžžããã¹ããSSLæ¥ç¶ãçµäºããã埩å·åããã³åæå·åããªãéãããã£ãã·ã³ã°ã¯äžéãã¹ãã§å®è¡ãããŸããã
ãªã³ããã³ãã®ã³ãŒã
ããã¯ããµãŒããŒãã¯ã©ã€ã¢ã³ããžã®å¿çã§å®è¡å¯èœã³ãŒããæäŸã§ããããšã瀺ããªãã·ã§ã³ã®èŠä»¶ã§ãã ãã®ååã§ã¯ãã¯ã©ã€ã¢ã³ããå®è¡å¯èœãªå®è¡å¯èœã³ãŒãã«ã€ããŠãµãŒããŒãšã¯ã©ã€ã¢ã³ãéã®åæãå¿ èŠã§ãããããããã¯APIã§ã»ãšãã©äœ¿çšãããŸããã ãµãŒããŒãWebãã©ãŠã¶ãŒãå®è¡ããããã«JavaScriptã³ãŒããè¿ããšæããããããŸããããRESTã¯Webãã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãå°çšã«èšèšãããŠããŸããã ããšãã°ãã¯ã©ã€ã¢ã³ããiOSãŸãã¯Androidããã€ã¹ã®å ŽåãJavaScriptã®å®è¡ã¯ããè€éã«ãªãå¯èœæ§ããããŸãã
ã¹ããŒãã¬ã¹
ã¹ããŒãã¬ã¹ã®ååã¯ãRESTã®çŽç²äž»çŸ©è ãšå®çšäž»çŸ©è ã®éã®ã»ãšãã©ã®è°è«ã®äžå¿ã«ãã2ã€ã®ãã¡ã®1ã€ã§ãã REST APIã¯ããã®ã¯ã©ã€ã¢ã³ãããªã¯ãšã¹ããéä¿¡ãããã³ã«åŒã³åºãããã¯ã©ã€ã¢ã³ãã®ç¶æ ãä¿åãã¹ãã§ã¯ãªããšè¿°ã¹ãŠããŸãã ããã¯ãã¢ããªã±ãŒã·ã§ã³ã®ããŒãžãããã²ãŒããããšãã«ãŠãŒã¶ãŒããèšæ¶ãããWebéçºã§äžè¬çãªã¡ã«ããºã ã¯äœ¿çšã§ããªãããšãæå³ããŸãã ã¹ããŒãã¬ã¹APIã§ã¯ãåãªã¯ãšã¹ãã«ããµãŒããŒãã¯ã©ã€ã¢ã³ããèå¥ããã³èªèšŒãããªã¯ãšã¹ããå®äºããå¿ èŠãããæ å ±ãå«ããå¿ èŠããããŸãã ãŸãããµãŒããŒã¯ã¯ã©ã€ã¢ã³ãæ¥ç¶ã«é¢é£ããããŒã¿ãããŒã¿ããŒã¹ãŸãã¯ãã®ä»ã®åœ¢åŒã®ã¹ãã¬ãŒãžã«ä¿åã§ããªãããšãæå³ããŸãã
ãªãRESTãã¹ããŒãã¬ã¹ãµãŒããŒãå¿ èŠãšããã®ãçåã«æã£ãŠããå Žåãäž»ãªçç±ã¯ãã¹ããŒãã¬ã¹ãµãŒããŒã®ã¹ã±ãŒãªã³ã°ãéåžžã«ç°¡åã§ãããŒããã©ã³ãµãŒã®èåŸã§è€æ°ã®ãµãŒããŒã€ã³ã¹ã¿ã³ã¹ãèµ·åããã ãã§ããããšã§ãã ãµãŒããŒãã¯ã©ã€ã¢ã³ãã®ç¶æ ãä¿åããå Žåãè€æ°ã®ãµãŒããŒããã®ç¶æ ã«ã¢ã¯ã»ã¹ããŠæŽæ°ããæ¹æ³ã調ã¹ãå¿ èŠãããããããŸãã¯ãã®ã¯ã©ã€ã¢ã³ããåžžã«ã¹ãã£ãããŒã»ãã·ã§ã³ãšåŒã°ããåããµãŒããŒã«ãã£ãŠåžžã«åŠçãããããã«ããå¿ èŠããããããç¶æ³ã¯ããè€éã«ãªããŸãã
ç« ã®åé ã§èª¬æãã/翻蚳ã«ãŒããããäžåºŠèŠããšããã®ã«ãŒãã«é¢é£ä»ãããããã¥ãŒé¢æ°ã¯Flask-Loginã®@login_required
ãã³ãŒããŒã«äŸåããŠãããããRESTfulãšã¯èŠãªãããªãããšã@login_required
ãŸãã FlaskãŠãŒã¶ãŒã»ãã·ã§ã³ã®ãŠãŒã¶ãŒç¶æ
ã
çµ±äžãããã€ã³ã¿ãŒãã§ãŒã¹
æåŸã«ãæãéèŠã§ãæãè°è«ãããæãææ§ã«ææžåãããRESTååã¯ãåäžã®ã€ã³ã¿ãŒãã§ãŒã¹ã§ãã ãã£ãŒã«ãã£ã³ã°å士ã¯ãåäžã®RESTã€ã³ã¿ãŒãã§ãŒã¹ã®4ã€ã®ç¹åŸŽçãªåŽé¢ãåæããŠããŸãïŒãŠããŒã¯ãªãªãœãŒã¹èå¥åããªãœãŒã¹è¡šçŸãèªå·±èšè¿°ã¡ãã»ãŒãžããã€ããŒã¡ãã£ã¢ã
äžæã®ãªãœãŒã¹èå¥åã¯ãäžæã®URLãåãªãœãŒã¹ã«å²ãåœãŠãããšã«ãã£ãŠååŸãããŸãã ããšãã°ããã®ãŠãŒã¶ãŒã«é¢é£ä»ããããŠããURLã¯ã/ api / users / <user-id>ã§ããããã§ã<user-id>ã¯ãããŒã¿ããŒã¹ããŒãã«ã®ãã©ã€ããªããŒãšããŠãŠãŒã¶ãŒã«å²ãåœãŠãããèå¥åã§ãã ããã¯ã»ãšãã©ã®APIã§å®å šã«åãå ¥ããããŸãã
ãªãœãŒã¹è¡šçŸã®äœ¿çšã¯ããµãŒããŒãšã¯ã©ã€ã¢ã³ãããªãœãŒã¹ã«é¢ããæ å ±ã亀æããå Žåãäžè²«ãã圢åŒã䜿çšããå¿ èŠãããããšãæå³ããŸãã æè¿ã®ã»ãšãã©ã®APIã§ã¯ãJSON圢åŒã䜿çšããŠãªãœãŒã¹è¡šçŸãæ§ç¯ããŸãã APIã¯ããªãœãŒã¹ãè¡šãããã®ããã€ãã®åœ¢åŒããµããŒãã§ããŸãããã®å ŽåãHTTPãããã³ã«ã®ã³ã³ãã³ãããŽã·ãšãŒã·ã§ã³ãã©ã¡ãŒã¿ãŒã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒãäž¡æ¹ã®ãŠãŒã¶ãŒã奜ã圢åŒãããŽã·ãšãŒãã§ããã¡ã«ããºã ã§ãã
èªå·±èšè¿°ã¡ãã»ãŒãžã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒéã§äº€æãããèŠæ±ãšå¿çã«ãçžæãå¿
èŠãšãããã¹ãŠã®æ
å ±ãå«ããå¿
èŠãããããšãæå³ããŸãã å
žåçãªäŸã¯ãã¯ã©ã€ã¢ã³ãããµãŒããŒããåä¿¡ãããæäœã瀺ãããã«äœ¿çšãããHTTPèŠæ±ã¡ãœããã§ãã GET
ã¯ãã¯ã©ã€ã¢ã³ãããªãœãŒã¹ã«é¢ããæ
å ±ãååŸãããããšã瀺ãã POST
èŠæ±ã¯ã¯ã©ã€ã¢ã³ããæ°ãããªãœãŒã¹ãäœæãããããšã瀺ãã PUT
ãŸãã¯PATCH
èŠæ±ã¯æ¢åã®ãªãœãŒã¹ãžã®å€æŽã決å®ãã DELETE
èŠæ±ã¯ãªãœãŒã¹ã®åé€ã瀺ããŸãã ã¿ãŒã²ãããªãœãŒã¹ã¯ãHTTPããããŒãURLã®ãªã¯ãšã¹ãæååã®äžéšããŸãã¯ãªã¯ãšã¹ãæ¬æã§æäŸãããè¿œå æ
å ±ãšãšãã«ããªã¯ãšã¹ãURLãšããŠæå®ãããŸãã
ãã€ããŒã¡ãã£ã¢ã®èŠä»¶ã¯ãå€ãã®ç©è°ãéžããã®ã§ãããå°æ°ã®APIã§å®è£ ãããŠãããã®ã§ããããããå®è£ ããAPIã¯ãRESTã®çŽç²äž»çŸ©è ãæºè¶³ãããããã«ãã£ãã«ããããŸããã ã¢ããªã±ãŒã·ã§ã³å ã®ãã¹ãŠã®ãªãœãŒã¹ãçžäºæ¥ç¶ãããŠããããããªãœãŒã¹ãã¥ãŒã«ãªã³ã¯ãå«ããå¿ èŠããããŸããããã«ããã顧客ããªã³ã¯ããã©ããŒã¹ããŠæ°ãããªãœãŒã¹ãèŠã€ããããšãã§ããŸããããäžã€ã ã¯ã©ã€ã¢ã³ãã¯ããªãœãŒã¹ãäºåã«ç¥ããªããŠãAPIã«ã¢ã¯ã»ã¹ãããã€ããŒã¡ãã£ã¢ãªã³ã¯ããã©ãã ãã§APIã«ã€ããŠç¥ãããšãã§ããŸãã ãã®èŠä»¶ã®å®è£ ãè€éã«ããåŽé¢ã®1ã€ã¯ãHTMLãXMLãšã¯ç°ãªããéåžžAPIã§ãªãœãŒã¹ãè¡šãããã«äœ¿çšãããjson圢åŒã§ã¯ããªã³ã¯ãå«ããæšæºçãªæ¹æ³ãå®çŸ©ãããŠããªããããç¹å¥ãªã«ã¹ã¿ã æ§é ããŸãã¯JSON-API ã HAL ã JSON-LDãªã©ããã®ã®ã£ãããåããããšããææ¡ãããJSONæ¡åŒµã®
ãã«ãŒããªã³ãAPIã³ã³ã»ããã®å®è£
APIã®éçºã«é¢ä¿ãããã®ã®ã¢ã€ãã¢ãæäŸããããã«ããã€ã¯ãããã°ã«è¿œå ããŸãã ããã¯å®å šãªAPIã§ã¯ãããŸããããŠãŒã¶ãŒã«é¢é£ãããã¹ãŠã®æ©èœãå®è£ ããèªè ãžã®ããã°æçš¿ãªã©ãä»ã®ãªãœãŒã¹ã®å®è£ ã¯æŒç¿ãšããŠæ®ããŸãã
第15ç« ã§èª¬æããæŠå¿µã«åŸã£ãŠãã¹ãŠãç·šæããã³æ§é åãããããã«ããã¹ãŠã®APIã«ãŒããå«ãæ°ãããããžã§ã¯ããäœæããŸãã ããã§ã¯ããã®ãããžã§ã¯ããååšãããã£ã¬ã¯ããªãäœæããããšããå§ããŸãããã
(venv) $ mkdir app/api
ãã«ãŒããªã³ã__init __. py
ãã¡ã€ã«__init __. py
__init __. py
ã¯ãä»ã®ãã«ãŒããªã³ãã¢ããªã±ãŒã·ã§ã³ãšåæ§ã®ãã«ãŒããªã³ããªããžã§ã¯ããäœæããŸãã
app/api/__init__.py:
constructor APIã
from flask import Blueprint bp = Blueprint('api', __name__) from app.api import users, errors, tokens
埪ç°äŸåãšã©ãŒãåé¿ããããã«ãã€ã³ããŒããã¢ãžã¥ãŒã«ã®äžçªäžã«ç§»åããå¿ èŠãããå ŽåãããããšãããããèŠããŠããã§ãããã ãããapp / api / users.py ã app / api / errors.pyããã³app / api / tokens.pyã¢ãžã¥ãŒã«ïŒããã¯ãŸã æžããŠããŸããïŒããããžã§ã¯ãã®äœæåŸã«ã€ã³ããŒããããçç±ã§ã ã
APIã®ã¡ã€ã³ã³ã³ãã³ãã¯app / api / users.pyã¢ãžã¥ãŒã«ã«ä¿åãããŸã ã 次ã®è¡šã«ãå®è£ ããã«ãŒãã瀺ããŸãã
HTTPã¡ãœãã | ãªãœãŒã¹URL | 泚é |
---|---|---|
ã²ãã | /api/users/<id>
| ãŠãŒã¶ãŒãè¿ããŸãã |
ã²ãã | /api/users
| ãã¹ãŠã®ãŠãŒã¶ãŒã®ã³ã¬ã¯ã·ã§ã³ãè¿ããŸãã |
ã²ãã | /api/users/<id>/followers
| ãã®ãŠãŒã¶ãŒã®ãã©ãã¯ãŒãè¿ããŸãã |
ã²ãã | /api/users/<id>/followed
| ãã®ãŠãŒã¶ãŒããã©ããŒããŠãããŠãŒã¶ãŒãè¿ããŸãã |
æçš¿ | /api/users
| æ°ãããŠãŒã¶ãŒã¢ã«ãŠã³ããç»é²ããŸãã |
眮ã | /api/users/<id>
| ãŠãŒã¶ãŒãå€æŽããŸãã |
ããããã¹ãŠã®ã«ãŒãã®ãã¬ãŒã¹ãã«ããŒãå«ãã¢ãžã¥ãŒã«ãã¬ãŒã ã¯ãŒã¯ã¯æ¬¡ã®ããã«ãªããŸãã
app/api/users.py:
ãŠãŒã¶ãŒAPIãªãœãŒã¹ãã¬ãŒã¹ãã«ããŒã
from app.api import bp @bp.route('/users/<int:id>', methods=['GET']) def get_user(id): pass @bp.route('/users', methods=['GET']) def get_users(): pass @bp.route('/users/<int:id>/followers', methods=['GET']) def get_followers(id): pass @bp.route('/users/<int:id>/followed', methods=['GET']) def get_followed(id): pass @bp.route('/users', methods=['POST']) def create_user(): pass @bp.route('/users/<int:id>', methods=['PUT']) def update_user(id): pass
app / api / errors.pyã¢ãžã¥ãŒã«ã§ã¯ããšã©ãŒå¿çãåŠçããããã€ãã®ãã«ããŒé¢æ°ãå®çŸ©ããå¿ èŠããããŸãã ããããä»ãåŸã§å ¥åãããã¬ãŒã¹ãã«ããŒãäœæããŸãã
app/api/errors.py:
ãã¬ãŒã¹ãã«ããŒã®åŠçãšã©ãŒã
def bad_request(): pass
èªèšŒãµãã·ã¹ãã ãå®çŸ©ãããapp / api / tokens.pyã¢ãžã¥ãŒã«ã ããã«ãããWebãã©ãŠã¶ãŒã§ã¯ãªãã¯ã©ã€ã¢ã³ãã«ä»£æ¿ãã°ã€ã³æ¹æ³ãæäŸãããŸãã ãã®ã¢ãžã¥ãŒã«ã®ãã¬ãŒã¹ãã«ããŒãæžããŸãããïŒ
app/api/tokens.py:
ããŒã«ãŒãåŠçããŸãã
def get_token(): pass def revoke_token(): pass
æ°ããBlueprint APIã¹ããŒãã¯ãã¢ããªã±ãŒã·ã§ã³ãã¡ã¯ããªé¢æ°ã«ç»é²ããå¿ èŠããããŸãã
app/__init__.py:
èŠçŽ å³ãã¢ããªã±ãŒã·ã§ã³ã«ç»é²ããŸãã
# ... def create_app(config_class=Config): app = Flask(__name__) # ... from app.api import bp as api_bp app.register_blueprint(api_bp, url_prefix='/api') # ...
JSONãªããžã§ã¯ãã®åœ¢åŒã§ã®ãŠãŒã¶ãŒã®è¡šçŸ
APIãå®è£ ãããšãã«èæ ®ããæåã®åŽé¢ã¯ããªãœãŒã¹ã®ãã¬ãŒã³ããŒã·ã§ã³ã決å®ããããšã§ãã ãŠãŒã¶ãŒãšé£æºããAPIãå®è£ ããããããŠãŒã¶ãŒãªãœãŒã¹ã®ãã¥ãŒã解決ããå¿ èŠããããŸãã ããã€ãã®ãã¬ãŒã³ã¹ããŒãã³ã°ã®åŸã次ã®jsonãã¥ãŒãæãã€ããŸããã
{ "id": 123, "username": "susan", "password": "my-password", "email": "susan@example.com", "last_seen": "2017-10-20T15:04:27Z", "about_me": "Hello, my name is Susan!", "post_count": 7, "follower_count": 35, "followed_count": 21, "_links": { "self": "/api/users/123", "followers": "/api/users/123/followers", "followed": "/api/users/123/followed", "avatar": "https://www.gravatar.com/avatar/..." } }
ãã£ãŒã«ãã®å€ãã¯ããŠãŒã¶ãŒããŒã¿ããŒã¹ã¢ãã«ããçŽæ¥ååŸãããŸãã password
ãã£ãŒã«ãã¯ãæ°ãããŠãŒã¶ãŒãç»é²ãããšãã«ã®ã¿äœ¿çšããããšããç¹ã§ç°ãªããŸãã 第5ç« ã§ããããã®ããã«ããŠãŒã¶ãŒãã¹ã¯ãŒãã¯ããŒã¿ããŒã¹ã«ä¿åããããããã·ã¥ã®ã¿ãä¿åãããããããã¹ã¯ãŒããè¿ãããããšã¯ãããŸããã ãŠãŒã¶ãŒã®ã¡ãŒã«ã¢ãã¬ã¹ãå
¬éããããªãã®ã§ã email
ãã£ãŒã«ããç¹å¥ã«åŠçãããŸãã é»åã¡ãŒã«ãã£ãŒã«ãã¯ããŠãŒã¶ãŒãèªåã®ãšã³ããªãèŠæ±ããå Žåã«ã®ã¿è¿ãããŸãããä»ã®ãŠãŒã¶ãŒãããšã³ããªãåä¿¡ããå Žåã¯è¿ãããŸããã ãã£ãŒã«ãpost_count
ã follower_count
ããã³follow_count
ã¯ãããŒã¿ããŒã¹å
ã®ãã£ãŒã«ããšããŠã¯ååšããªããã䟿å®äžã¯ã©ã€ã¢ã³ãã«æäŸããããä»®æ³ããã£ãŒã«ãã§ãã ããã¯çŽ æŽãããäŸã§ããããªãœãŒã¹ã®è¡šç€ºã¯ãå®éã®ãªãœãŒã¹ããµãŒããŒäžã§å®çŸ©ãããŠããæ¹æ³ãšäžèŽããå¿
èŠããªãããšã瀺ããŠããŸãã
ãã€ããŒ_links
èŠä»¶ãå®è£
ãã_links
ã»ã¯ã·ã§ã³ã_links
ãã ããã ç¹å®ã®ãªã³ã¯ã«ã¯ãçŸåšã®ãªãœãŒã¹ãžã®ãªã³ã¯ããã®ãŠãŒã¶ãŒããã©ããŒããŠãããŠãŒã¶ãŒã®ãªã¹ãããŠãŒã¶ãŒããã©ããŒããŠãããŠãŒã¶ãŒã®ãªã¹ããæåŸã«ãŠãŒã¶ãŒã®ã¢ãã¿ãŒç»åãžã®ãªã³ã¯ãå«ãŸããŸãã å°æ¥ããã®APIã«ã¡ãã»ãŒãžãè¿œå ããããšã«ããå ŽåããŠãŒã¶ãŒã¡ãã»ãŒãžã®ãªã¹ããžã®ãªã³ã¯ãããã«å«ããå¿
èŠããããŸãã
JSON圢åŒã®åªããç¹ã®1ã€ã¯ãåžžã«PythonèŸæžãŸãã¯ãªã¹ããã¥ãŒãšããŠå€æãããããšã§ãã Pythonæšæºã©ã€ãã©ãªã®json
ããã±ãŒãžã¯ãPythonããŒã¿æ§é ãšJSONã®å€æãåŠçããŸãã ãããã£ãŠããããã®ãã¥ãŒãçæããããã«ãPythonèŸæžãè¿ãto_dict()
ãšããUser
ã¢ãã«ã«ã¡ãœãããè¿œå ããŸãã
app/models.py:
ãã¥ãŒã®ãŠãŒã¶ãŒã¢ãã«ã
from flask import url_for # ... class User(UserMixin, db.Model): # ... def to_dict(self, include_email=False): data = { 'id': self.id, 'username': self.username, 'last_seen': self.last_seen.isoformat() + 'Z', 'about_me': self.about_me, 'post_count': self.posts.count(), 'follower_count': self.followers.count(), 'followed_count': self.followed.count(), '_links': { 'self': url_for('api.get_user', id=self.id), 'followers': url_for('api.get_followers', id=self.id), 'followed': url_for('api.get_followed', id=self.id), 'avatar': self.avatar(128) } } if include_email: data['email'] = self.email return data
ãã®æ¹æ³ã¯ç¹å¥ãªè³ªåãåŒãèµ·ãããã®ã§ã¯ãªããåºæ¬çã«æ確ã«ããå¿
èŠããããŸãã åæ¢ãããŠãŒã¶ãŒãã¥ãŒã®ãã£ã¯ã·ã§ããªãçæãããŠè¿ãããŸãã äžã§è¿°ã¹ãããã«ããŠãŒã¶ãŒãèªåã®ããŒã¿ãèŠæ±ãããšãã«ã®ã¿ã¡ãŒã«ãæå¹ã«ãããã®ã§ã email
ãã£ãŒã«ãã«ã¯ç¹å¥ãªåŠçãå¿
èŠã§ãã ãããã£ãŠã include_email
ãã©ã°ã䜿çšããŠããã®ãã£ãŒã«ãããã¥ãŒã«å«ãŸããŠãããã©ãããå€æãinclude_email
ã
last_seen
ãã£ãŒã«ããã©ã®ããã«last_seen
ãã«æ³šç®ããŠlast_seen
ã æ¥ä»ãšæå»ã®ãã£ãŒã«ãã«ã¯ã ISO 8601圢åŒã䜿çšããŸãisoformat()
ã¯isoformat()
ã¡ãœããã䜿çšããŠçæã§ããŸãã ããããUTCã§ãããç¶æ
ã«èšé²ãããã¿ã€ã ãŸãŒã³ãæããªãåçŽãªdatetime
ãªããžã§ã¯ãã䜿çšãããããUTCã®ISO 8601ã¿ã€ã ãŸãŒã³ã³ãŒãã§ããZ
ãæåŸã«è¿œå ããå¿
èŠããããŸãã
amkkoããã®èª¬æïŒ pythonã§ã¯ãdatetimeãªããžã§ã¯ãã¯ã¿ã€ã ãŸãŒã³ã«é¢ããŠãåçŽããŸãã¯ãåçŽ/èªèãã«ãªããŸãã
æåŸã«ããã€ããŒã¡ãã£ã¢ãªã³ã¯ã®å®è£
æ¹æ³ã確èªããŸãã ä»ã®ã¢ããªã±ãŒã·ã§ã³ã«ãŒããæã3ã€ã®ãªã³ã¯ã®å Žåã url_for()
ã䜿çšããŠURLïŒçŸåšapp / api / users.pyã§å®çŸ©ãããŠãã眮æèŠçŽ ã®ã«ãã¯ã¢ããé¢æ°ãæããŸãurl_for()
ãçæããŸãã ã¢ãã¿ãŒãªã³ã¯ã¯ãã¢ããªã±ãŒã·ã§ã³ã®å€éšã®Gravatar URLã§ãããããç¹å¥ã§ãã avatar()
, -.
to_dict()
Python, JSON. , , User
. from_dict()
, Python :
app/models.py:
.
class User(UserMixin, db.Model): # ... def from_dict(self, data, new_user=False): for field in ['username', 'email', 'about_me']: if field in data: setattr(self, field, data[field]) if new_user and 'password' in data: self.set_password(data['password'])
, : username
, email
about_me
. , data
, , setattr()
Python, .
password
, . new_user
, , , . password
, set_password()
, .
, API . , , , . :
{ "items": [ { ... user resource ... }, { ... user resource ... }, ... ], "_meta": { "page": 1, "per_page": 10, "total_pages": 20, "total_items": 195 }, "_links": { "self": "http://localhost:5000/api/users?page=1", "next": "http://localhost:5000/api/users?page=2", "prev": null } }
items
- , , . _meta
, . _links
, , , .
- , , , , API , , . 16 , , , . , , , SearchableMixin
, , . , mixin, PaginatedAPIMixin
:
app/models.py:
mixin.
class PaginatedAPIMixin(object): @staticmethod def to_collection_dict(query, page, per_page, endpoint, **kwargs): resources = query.paginate(page, per_page, False) data = { 'items': [item.to_dict() for item in resources.items], '_meta': { 'page': page, 'per_page': per_page, 'total_pages': resources.pages, 'total_items': resources.total }, '_links': { 'self': url_for(endpoint, page=page, per_page=per_page, **kwargs), 'next': url_for(endpoint, page=page + 1, per_page=per_page, **kwargs) if resources.has_next else None, 'prev': url_for(endpoint, page=page - 1, per_page=per_page, **kwargs) if resources.has_prev else None } } return data
to_collection_dict()
, items
, _meta
_links
. , , . - Flask-SQLAlchemy, . , . paginate()
, , , -.
, . , , , url_for ('api.get_users', id = id, page = page)
. url_for()
, , url_for()
. , kwargs
url_for()
. page
per_page
, API.
mixin User
:
app/models.py:
PaginatedAPIMixin .
class User(PaginatedAPIMixin, UserMixin, db.Model): # ...
, , .
ãšã©ãŒåŠç
, 7 , , , -. API , « » , , . API JSON, API. , :
{ "error": "short error description", "message": "error message (optional)" }
HTTP . , error_response()
app/api/errors.py :
app/api/errors.py:
.
from flask import jsonify from werkzeug.http import HTTP_STATUS_CODES def error_response(status_code, message=None): payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')} if message: payload['message'] = message response = jsonify(payload) response.status_code = status_code return response
HTTP_STATUS_CODES
Werkzeug
( Flask), HTTP. error
, . jsonify()
Response
Flask 200, .
, API , 400, " ". -, , , . , , . bad_request()
, :
app/api/errors.py:
.
# ... def bad_request(message): return error_response(400, message)
, JSON, , API.
, id
:
app/api/users.py:
.
from flask import jsonify from app.models import User @bp.route('/users/<int:id>', methods=['GET']) def get_user(id): return jsonify(User.query.get_or_404(id).to_dict())
view URL-. get_or_404()
get()
, , , , , None
, id
, 404 . get_or_404()
get()
, , .
to_dict()
, User
, , Flask jsonify()
JSON .
, API, , URL- :
http://localhost:5000/api/users/1
, JSON. id
, , get_or_404()
SQLAlchemy 404 ( , , JSON).
, HTTPie , HTTP- , Python, API:
(venv) $ pip install httpie
1 (, , ) :
(venv) $ http GET http://localhost:5000/api/users/1 HTTP/1.0 200 OK Content-Length: 457 Content-Type: application/json Date: Mon, 27 Nov 2017 20:19:01 GMT Server: Werkzeug/0.12.2 Python/3.6.3 { "_links": { "avatar": "https://www.gravatar.com/avatar/993c...2724?d=identicon&s=128", "followed": "/api/users/1/followed", "followers": "/api/users/1/followers", "self": "/api/users/1" }, "about_me": "Hello! I'm the author of the Flask Mega-Tutorial.", "followed_count": 0, "follower_count": 1, "id": 1, "last_seen": "2017-11-26T07:40:52.942865Z", "post_count": 10, "username": "miguel" }
, to_collection_dict()
PaginatedAPIMixin:
app/api/users.py: .
from flask import request @bp.route('/users', methods=['GET']) def get_users(): page = request.args.get('page', 1, type=int) per_page = min(request.args.get('per_page', 10, type=int), 100) data = User.to_collection_dict(User.query, page, per_page, 'api.get_users') return jsonify(data)
page
per_page
, 1 10 , . per_page
, 100. , . page
per_page
to_collection_query()
, User.query
- , . - api.get_users
, , .
HTTPie, :
(venv) $ http GET http://localhost:5000/api/users The next two endpoints are the ones that return the follower and followed users. These are fairly similar to the one above: app/api/users.py: Return followers and followed users. @bp.route('/users/<int:id>/followers', methods=['GET']) def get_followers(id): user = User.query.get_or_404(id) page = request.args.get('page', 1, type=int) per_page = min(request.args.get('per_page', 10, type=int), 100) data = User.to_collection_dict(user.followers, page, per_page, 'api.get_followers', id=id) return jsonify(data) @bp.route('/users/<int:id>/followed', methods=['GET']) def get_followed(id): user = User.query.get_or_404(id) page = request.args.get('page', 1, type=int) per_page = min(request.args.get('per_page', 10, type=int), 100) data = User.to_collection_dict(user.followed, page, per_page, 'api.get_followed', id=id) return jsonify(data)
, id
. , user.followers
user.followed
to_collection_dict()
, , , , . to_collection_dict()
â , kwargs
, url_for()
.
, HTTPie :
(venv) $ http GET http://localhost:5000/api/users/1/followers (venv) $ http GET http://localhost:5000/api/users/1/followed
, hypermedia URL-, _links
.
POST
/users . :
app/api/users.py:
.
from flask import url_for from app import db from app.api.errors import bad_request @bp.route('/users', methods=['POST']) def create_user(): data = request.get_json() or {} if 'username' not in data or 'email' not in data or 'password' not in data: return bad_request('must include username, email and password fields') if User.query.filter_by(username=data['username']).first(): return bad_request('please use a different username') if User.query.filter_by(email=data['email']).first(): return bad_request('please use a different email address') user = User() user.from_dict(data, new_user=True) db.session.add(user) db.session.commit() response = jsonify(user.to_dict()) response.status_code = 201 response.headers['Location'] = url_for('api.get_user', id=user.id) return response
JSON , . Flask request.get_json()
, JSON Python. None
, JSON , , , request.get_json()
{}
.
, , , , . username
, email
password
. - , bad_request()
app/api/errors.py . , username
email
, , - , .
, , . from_dict()
. new_user
True
, password
, .
, , , to_dict()
. POST
, , 201
, , . , HTTP , 201
Location, URL- .
, HTTPie:
(venv) $ http POST http://localhost:5000/api/users username=alice password=dog \ email=alice@example.com "about_me=Hello, my name is Alice!"
, API, â , :
app/api/users.py:
.
@bp.route('/users/<int:id>', methods=['PUT']) def update_user(id): user = User.query.get_or_404(id) data = request.get_json() or {} if 'username' in data and data['username'] != user.username and \ User.query.filter_by(username=data['username']).first(): return bad_request('please use a different username') if 'email' in data and data['email'] != user.email and \ User.query.filter_by(email=data['email']).first(): return bad_request('please use a different email address') user.from_dict(data, new_user=False) db.session.commit() return jsonify(user.to_dict())
id
URL, 404
, . , , username
email
, , , , . , , , . , , , , , , . - , 400
, .
From_dict()
, , . 200
.
, about_me
HTTPie:
(venv) $ http PUT http://localhost:5000/api/users/2 "about_me=Hi, I am Miguel"
API
API, , . , , , «AuthN» «AuthZ» . , , , , , , , .
API @login_required
Flask-Login, . , HTML . API HTML , , , 401. , API - HTML. API 401, , , , .
()
API . API, , . API, , , . . User
:
app/models.py:
.
import base64 from datetime import datetime, timedelta import os class User(UserMixin, PaginatedAPIMixin, db.Model): # ... token = db.Column(db.String(32), index=True, unique=True) token_expiration = db.Column(db.DateTime) # ... def get_token(self, expires_in=3600): now = datetime.utcnow() if self.token and self.token_expiration > now + timedelta(seconds=60): return self.token self.token = base64.b64encode(os.urandom(24)).decode('utf-8') self.token_expiration = now + timedelta(seconds=expires_in) db.session.add(self) return self.token def revoke_token(self): self.token_expiration = datetime.utcnow() - timedelta(seconds=1) @staticmethod def check_token(token): user = User.query.filter_by(token=token).first() if user is None or user.token_expiration < datetime.utcnow(): return None return user
token
, , . token_expiration
, . , , .
. get_token()
. , base64, . , , .
, . , . revoke_token()
, , , .
check_token()
, , . , None.
, , :
(venv) $ flask db migrate -m "user tokens" (venv) $ flask db upgrade
()
API, , -, -. API , , . API, , -.
, Flask Flask-HTTPAuth . Flask-HTTPAuth pip:
(venv) $ pip install flask-httpauth
Flask-HTTPAuth , API . HTTP Basic Authentication 11.1 , http . Flask-HTTPAuth : , , , , . Flask-HTTPAuth , . :
app/api/auth.py:
.
from flask import g from flask_httpauth import HTTPBasicAuth from app.models import User from app.api.errors import error_response basic_auth = HTTPBasicAuth() @basic_auth.verify_password def verify_password(username, password): user = User.query.filter_by(username=username).first() if user is None: return False g.current_user = user return user.check_password(password) @basic_auth.error_handler def basic_auth_error(): return error_response(401)
HTTPBasicAuth
Flask-HTTPAuth- , . verify_password
error_handler
.
, , True
, , False
, . check_password()
User
, Flask-Login -. g.current_user
, API.
401, error_response()
app/api/errors.py . 401 HTTP "Unauthorized" (" "). HTTP , .
, , , :
app/api/tokens.py:
Generate user tokens.
from flask import jsonify, g from app import db from app.api import bp from app.api.auth import basic_auth @bp.route('/tokens', methods=['POST']) @basic_auth.login_required def get_token(): token = g.current_user.get_token() db.session.commit() return jsonify({'token': token})
@basic_auth.login_required
HTTPBasicAuth, Flask-HTTPAuth ( ) , . get_token()
. , , .
POST API :
(venv) $ http POST http://localhost:5000/api/tokens HTTP/1.0 401 UNAUTHORIZED Content-Length: 30 Content-Type: application/json Date: Mon, 27 Nov 2017 20:01:00 GMT Server: Werkzeug/0.12.2 Python/3.6.3 WWW-Authenticate: Basic realm="Authentication Required" { "error": "Unauthorized" }
HTTP 401 , basic_auth_error()
. , :
(venv) $ http --auth <username>:<password> POST http://localhost:5000/api/tokens HTTP/1.0 200 OK Content-Length: 50 Content-Type: application/json Date: Mon, 27 Nov 2017 20:01:22 GMT Server: Werkzeug/0.12.2 Python/3.6.3 { "token": "pC1Nu9wwyNt8VCj1trWilFdFI276AcbS" }
200, , . , <username>:<password>
. .
API
API, . , Flask-HTTPAuth . HTTPTokenAuth
:
app/api/auth.py:
Token.
# ... from flask_httpauth import HTTPTokenAuth # ... token_auth = HTTPTokenAuth() # ... @token_auth.verify_token def verify_token(token): g.current_user = User.check_token(token) if token else None return g.current_user is not None @token_auth.error_handler def token_auth_error(): return error_response(401)
Flask-HTTPAuth verify_token
, , . User.check_token()
, , . , None
. True
False
, Flask-HTTPAuth .
API , @token_auth.login_required
:
app/api/users.py:
Protect user routes with token authentication.
from app.api.auth import token_auth @bp.route('/users/<int:id>', methods=['GET']) @token_auth.login_required def get_user(id): # ... @bp.route('/users', methods=['GET']) @token_auth.login_required def get_users(): # ... @bp.route('/users/<int:id>/followers', methods=['GET']) @token_auth.login_required def get_followers(id): # ... @bp.route('/users/<int:id>/followed', methods=['GET']) @token_auth.login_required def get_followed(id): # ... @bp.route('/users', methods=['POST']) def create_user(): # ... @bp.route('/users/<int:id>', methods=['PUT']) @token_auth.login_required def update_user(id): # ...
, API, create_user()
, , , , .
, , 401. , Authorization
, /api/tokens . Flask-HTTPAuth , -, HTTPie. HTTPie --auth
, . -:
(venv) $ http GET http://localhost:5000/api/users/1 \ "Authorization:Bearer pC1Nu9wwyNt8VCj1trWilFdFI276AcbS"
, , , â , :
app/api/tokens.py:
Revoke tokens.
from app.api.auth import token_auth @bp.route('/tokens', methods=['DELETE']) @token_auth.login_required def revoke_token(): g.current_user.revoke_token() db.session.commit() return '', 204
DELETE
URL- /tokens , . , , Authorization
, . User
, . , . , . return 204, , .
, HTTPie:
(venv) $ http DELETE http://localhost:5000/api/tokens \ Authorization:"Bearer pC1Nu9wwyNt8VCj1trWilFdFI276AcbS"
API
, , API URL- ? 404, 404 HTML. , API, JSON API, , Flask, - , , HTML.
HTTP , , . Accept
, . , , , .
, HTML JSON . Flask request.accept_mimetypes
:
app/errors/handlers.py:
.
from flask import render_template, request from app import db from app.errors import bp from app.api.errors import error_response as api_error_response def wants_json_response(): return request.accept_mimetypes['application/json'] >= \ request.accept_mimetypes['text/html'] @bp.app_errorhandler(404) def not_found_error(error): if wants_json_response(): return api_error_response(404) return render_template('errors/404.html'), 404 @bp.app_errorhandler(500) def internal_error(error): db.session.rollback() if wants_json_response(): return api_error_response(500) return render_template('errors/500.html'), 500
ãã«ããŒé¢æ°wants_json_response()
ã¯ãåªå
圢åŒã®ãªã¹ãã§ã¯ã©ã€ã¢ã³ããéžæããJSONãŸãã¯HTMLã®èšå®ãæ¯èŒããŸããJSONã®é床ãHTMLãããéãå ŽåãJSONå¿çãè¿ããŸãããã以å€ã®å Žåãå
ã®ãã³ãã¬ãŒãããŒã¹ã®HTMLå¿çãè¿ããŸããJSONã®åçã«ã€ããŠerror_response
ã¯ãAPIèŠçŽ ã®ã¹ããŒããããã«ããŒé¢æ°ãã€ã³ããŒãããŸãããããã§ååãå€æŽããŠapi_error_response()
ããã®æ©èœãšçæå
ãæ確ã«ãªãããã«ããŸãã