Top Banner
29

Building Web Apps With Go

Feb 01, 2016

Download

Documents

Dev Crc

laravel5essencial
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Building Web Apps With Go
Page 2: Building Web Apps With Go

1. Introduction2. GoMakesThingsSimple3. Thenet/httppackage4. CreatingaBasicWebApp5. Deployment6. URLRouting7. Middleware8. Rendering

i. JSONii. HTMLTemplatesiii. UsingTherenderpackage

9. Testingi. UnitTestingii. EndtoEndTesting

10. Controllers11. Databases12. TipsandTricks13. MovingForward

TableofContents

BuildingWebAppswithGo

2

Page 3: Building Web Apps With Go

WelcometoBuildingWebAppswithGo!Ifyouarereadingthisthenyouhavejuststartedyourjourneyfromnoobtopro.Noseriously,webprogramminginGoissofunandeasythatyouwon'tevennoticehowmuchinformationyouarelearningalongtheway!

Keepinmindthattherearestillportionsofthisbookthatareincompleteandneedsomelove.ThebeautyofopensourcepublishingisthatIcangiveyouanincompletebookanditisstillofvaluetoyou.

Beforewegetintoallthenittygrittydetails,let'sstartwithsomegroundrules:

Tokeepthistutorialsmallandfocused,I'massumingthatyouarepreparedinthefollowingways:

1. YouhaveinstalledtheGoProgrammingLanguage.2. YouhavesetupaGOPATHbyfollowingtheHowtoWriteGoCodetutorial.3. YouaresomewhatfamiliarwiththebasicsofGo.(TheGoTourisaprettygoodplacetostart)4. Youhaveinstalledalltherequiredpackages5. YouhaveinstalledtheHerokuToolbelt6. YouhaveaHerokuaccount

Forthemostpartwewillbeusingthebuiltinpackagesfromthestandardlibrarytobuildoutourwebapps.CertainlessonssuchasDatabases,MiddlewareandURLRoutingwillrequireathirdpartypackage.Hereisalistofallthegopackagesyouwillneedtoinstallbeforestarting:

Name ImportPath Description

httprouter github.com/julienschmidt/httprouter AhighperformanceHTTPrequestrouterthatscaleswell

Negroni github.com/codegangsta/negroni IdiomaticHTTPMiddleware

BlackFriday github.com/russross/blackfriday amarkdownprocessor

Render gopkg.in/unrolled/render.v1 EasyrenderingforJSON,XML,andHTML

SQLite3 github.com/mattn/go-sqlite3 sqlite3driverforgo

Youcaninstall(orupdate)thesepackagesbyrunningthefollowingcommandinyourconsole

goget-u<import_path>

Forinstance,ifyouwishtoinstallNegroni,thefollowingcommandwouldbe:

goget-ugithub.com/codegangsta/negroni

Introduction

Prerequisites

RequiredPackages

BuildingWebAppswithGo

3Introduction

Page 4: Building Web Apps With Go

Ifyouhavebuiltawebapplicationbefore,yousurelyknowthattherearequitealotofconceptstokeepinyourhead.HTTP,HTML,CSS,JSON,databases,sessions,cookies,forms,middleware,routingandcontrollersarejustafewamongthemanythingsyourwebappmayneedtointeractwith.

Whileeachoneofthesethingscanbeimportantinthebuildingofyourwebapplications,noteveryoneofthemisimportantforanygivenapp.Forinstance,awebAPImayjustuseJSONasitsserializationformat,thusmakingconceptslikeHTMLnotrelevantforthatparticularwebapp.

TheGocommunityunderstandsthisdilemma.Ratherthanrelyonlarge,heavyweightframeworksthattrytocoverallthebases,Goprogrammerspullinthebarenecessitiestogetthejobdone.Thisminimalistapproachtowebprogrammingmaybeoff-puttingatfirst,buttheresultofthiseffortisamuchsimplerprogramintheend.

Gomakesthingssimple,it'saseasyasthat.Ifwetrainourselvestoalignwiththe"Goway"ofprogrammingfortheweb,wewillendupwithmoresimple,flexible,andmaintainablewebapplications.

Aswegothroughtheexercisesinthisbook,Ithinkyouwillbesurprisedbyhowsimplesomeoftheseprogramscanbewhilststillaffordingabunchoffunctionality.

WhensittingdowntocraftyourownwebapplicationsinGo,thinkhardaboutthecomponentsandconceptsthatyourappwillbefocusedon,andusejustthosepieces.Thisbookwillbecoveringawidearrayofwebtopics,butdonotfeelobligatedtousethemall.InthewordsofourfriendLonestar,"Takeonlywhatyouneedtosurvive".

GoMakesThingsSimple

TheGoWay

PowerinSimplicity

BuildingWebAppswithGo

4GoMakesThingsSimple

Page 5: Building Web Apps With Go

BuildingWebAppswithGo

5GoMakesThingsSimple

Page 6: Building Web Apps With Go

YouhaveprobablyheardthatGoisfantasticforbuildingwebapplicationsofallshapesandsizes.Thisispartlyduetothefantasticworkthathasbeenputintomakingthestandardlibraryclean,consistent,andeasytouse.

PerhapsoneofthemostimportantpackagesforanybuddingGowebdeveloperisthenet/httppackage.ThispackageallowsyoutobuildHTTPserversinGowithitspowerfulcompositionalconstructs.Beforewestartcoding,let'sdoanextremelyquickoverviewofHTTP.

Whenwetalkaboutbuildingwebapplications,weusuallymeanthatwearebuildingHTTPservers.HTTPisaprotocolthatwasoriginallydesignedtotransportHTMLdocumentsfromaservertoaclientwebbrowser.Today,HTTPisusedtotransportawholelotmorethanHTML.

TheimportantthingtonoticeinthisdiagramisthetwopointsofinteractionbetweentheServerandtheBrowser.TheBrowsermakesanHTTPrequestwithsomeinformation,theServerthenprocessesthatrequestandreturnsaResponse.

Thispatternofrequest-responseisoneofthekeyfocalpointsinbuildingwebapplicationsinGo.Infact,thenet/httppackage'smostimportantpieceisthehttp.HandlerInterface.

AsyoubecomemorefamiliarwithGo,youwillnoticehowmuchofanimpactinterfacesmakeinthedesignofyourprograms.Thenet/httpinterfaceencapsulatestherequest-responsepatterninonemethod:

Thenet/httpPackage

HTTPBasics

Thehttp.HandlerInterface

BuildingWebAppswithGo

6Thenet/httppackage

Page 7: Building Web Apps With Go

typeHandlerinterface{

ServeHTTP(ResponseWriter,*Request)

}

Implementorsofthisinterfaceareexpectedtoinspectandprocessdatacomingfromthehttp.Requestobjectandwriteoutaresponsetothehttp.ResponseWriterobject.

Thehttp.ResponseWriterinterfacelookslikethis:

typeResponseWriterinterface{

Header()Header

Write([]byte)(int,error)

WriteHeader(int)

}

Becausemuchofthenet/httppackageisbuiltoffofwelldefinedinterfacetypes,wecan(andareexpectedto)buildourwebapplicationswithcompositioninmind.Eachhttp.Handlerimplementationcanbethoughtofasitsownwebserver.

Manypatternscanbefoundinthatsimplebutpowerfulassumption.Throughoutthisbookwewillcoversomeofthesepatternsandhowwecanusethemtosolverealworldproblems.

Let'ssolvearealworldproblemin1lineofcode.

Mostofthetimepeoplejustneedtoservestaticfiles.MaybeyouhaveastaticHTMLlandingpageandjustwanttoserveupsomeHTML,images,andCSSandcallitaday.Sure,youcouldpullinApacheorPython'sSimpleHTTPServer,butApacheistoomuchforthislittlesiteandSimpleHTTPServeris,well,tooslow.

WewillbeginbycreatinganewprojectinourGOPATH.

cdGOPATH/src

mkdirfileserver&&cdfileserver

Createamain.gowithourtypicalgoboilerplate.

packagemain

import"net/http"

funcmain(){

}

Allweneedtoimportisthenet/httppackageforthistowork.RememberthatthisisallpartofthestandardlibraryinGo.

Let'swriteourfileservercode:

http.ListenAndServe(":8080",http.FileServer(http.Dir(".")))

ComposingWebServices

Exercise:1LineFileServer

BuildingWebAppswithGo

7Thenet/httppackage

Page 8: Building Web Apps With Go

Thehttp.ListenAndServefunctionisusedtostarttheserver,itwillbindtotheaddresswegaveit(:8080)andwhenitreceivesanHTTPrequest,itwillhanditofftothehttp.Handlerthatwesupplyasthesecondargument.Inourcaseitisthebuilt-inhttp.FileServer.

Thehttp.FileServerfunctionbuildsanhttp.Handlerthatwillserveanentiredirectoryoffilesandfigureoutwhichfiletoservebasedontherequestpath.WetoldtheFileServertoservethecurrentworkingdirectorywithhttp.Dir(".").

Theentireprogramlookslikethis:

packagemain

import"net/http"

funcmain(){

http.ListenAndServe(":8080",http.FileServer(http.Dir(".")))

}

Let'sbuildandrunourfileserverprogram:

gobuild

./fileserver

Ifwevisitlocalhost:8080/main.goweshouldseethecontentsofourmain.gofileinourwebbrowser.Wecanrunthisprogramfromanydirectoryandservethetreeasastaticfileserver.Allin1lineofGocode.

BuildingWebAppswithGo

8Thenet/httppackage

Page 9: Building Web Apps With Go

NowthatwearedonegoingoverthebasicsofHTTP,let'screateasimplebutusefulwebapplicationinGo.

Pullingfromourfileserverprogramthatweimplementedlastchapter,wewillimplementaMarkdowngeneratorusingthegithub.com/russross/blackfridaypackage.

Forstarters,wewillneedabasicHTMLformforthemarkdowninput:

<html>

<head>

<linkhref="/css/bootstrap.min.css"rel="stylesheet">

</head>

<body>

<divclass="container">

<divclass="page-title">

<h1>MarkdownGenerator</h1>

<pclass="lead">GenerateyourmarkdownwithGo</p>

<hr/>

</div>

<formaction="/markdown"method="POST">

<divclass="form-group">

<textareaclass="form-control"name="body"cols="30"rows="10"></textarea>

</div>

<divclass="form-group">

<inputtype="submit"class="btnbtn-primarypull-right"/>

</div>

</form>

</div>

<scriptsrc="/js/bootstrap.min.js"></script>

</body>

</html>

PutthisHTMLintoafilenamedindex.htmlinthe"public"folderofourapplicationandthebootstrap.min.cssfromhttp://getbootstrap.com/inthe"public/css"folder.NoticethattheformmakesanHTTPPOSTtothe"/markdown"endpointofourapplication.Wedon'tactuallyhandlethatrouterightnow,solet'saddit.

Theprogramtohandlethe'/markdown'routeandservethepublicindex.htmlfilelookslikethis:

packagemain

import(

"net/http"

"github.com/russross/blackfriday"

)

funcmain(){

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

http.ListenAndServe(":8080",nil)

}

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

CreatingaBasicWebApp

HTMLForm

The"/markdown"route

BuildingWebAppswithGo

9CreatingaBasicWebApp

Page 10: Building Web Apps With Go

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

Let'sbreakitdownintosmallerpiecestogetabetterideaofwhatisgoingon.

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

Weareusingthehttp.HandleFuncandhttp.Handlemethodstodefinesomesimpleroutingforourapplication.Itisimportanttonotethatcallinghttp.Handleonthe"/"patternwillactasacatch-allroute,sowedefinethatroutelast.http.FileServerreturnsanhttp.Handlersoweusehttp.Handletomapapatternstringtoahandler.Thealternativemethod,http.HandleFunc,usesanhttp.HandlerFuncinsteadofanhttp.Handler.Thismaybemoreconvenient,tothinkofhandlingroutesviaafunctioninsteadofanobject.

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

OurGenerateMarkdownfunctionimplementsthestandardhttp.HandlerFuncinterfaceandrendersHTMLfromaformfieldcontainingmarkdown-formattedtext.Inthiscase,thecontentisretrievedwithr.FormValue("body").Itisverycommontogetinputfromthehttp.Requestobjectthatthehttp.HandlerFuncreceivesasanargument.Someotherexamplesofinputarether.Header,r.Body,andr.URLmembers.

Wefinalizetherequestbywritingitouttoourhttp.ResponseWriter.Noticethatwedidn'texplicitlysendaresponsecode.Ifwewriteouttotheresponsewithoutacode,thenet/httppackagewillassumethattheresponseisa200OK.Thismeansthatifsomethingdidhappentogowrong,weshouldsettheresponsecodeviatherw.WriteHeader()method.

http.ListenAndServe(":8080",nil)

Thelastbitofthisprogramstartstheserver,wepassnilasourhandler,whichassumesthattheHTTPrequestswillbehandledbythenet/httppackagesdefaulthttp.ServeMux,whichisconfiguredusinghttp.Handleandhttp.HandleFunc,respectively.

AndthatisallyouneedtobeabletogeneratemarkdownasaserviceinGo.Itisasurprisinglysmallamountofcodefortheamountofheavyliftingitdoes.InthenextchapterwewilllearnhowtodeploythisapplicationtothewebusingHeroku.

BuildingWebAppswithGo

10CreatingaBasicWebApp

Page 11: Building Web Apps With Go

Herokumakesdeployingapplicationseasy.Itisaperfectplatformforsmalltomediumsizewebapplicationsthatarewillingtosacrificealittlebitofflexibilityininfrastructuretogainafairlypain-freeenvironmentfordeployingandmaintainingwebapplications.

IamchoosingtodeployourwebapplicationtoHerokuforthesakeofthistutorialbecauseinmyexperienceithasbeenthefastestwaytogetawebapplicationupandrunninginnotime.RememberthatthefocusofthistutorialishowtobuildwebapplicationsinGoandnotgettingcaughtupinallofthedistractionofprovisioning,configuring,deploying,andmaintainingthemachinesthatourGocodewillberunon.

Ifyoudon'talreadyhaveaHerokuaccount,signupatid.heroku.com/signup.It'squick,easyandfree.

ApplicationmanagementandconfigurationisdonethroughtheHerokutoolbelt,whichisafreecommandlinetoolmaintainedbyHeroku.WewillbeusingittocreateourapplicationonHeroku.Youcangetitfromtoolbelt.heroku.com.

TomakesuretheapplicationfromourlastchapterwillworkonHeroku,wewillneedtomakeafewchanges.HerokugivesusaPORTenvironmentvariableandexpectsourwebapplicationtobindtoit.Let'sstartbyimportingthe"os"packagesowecangrabthatPORTenvironmentvariable:

import(

"net/http"

"os"

"github.com/russross/blackfriday"

)

Next,weneedtograbthePORTenvironmentvariable,checkifitisset,andifitisweshouldbindtothatinsteadofourhardcodedport(8080).

port:=os.Getenv("PORT")

ifport==""{

port="8080"

}

Lastly,wewanttobindtothatportinourhttp.ListenAndServecall:

http.ListenAndServe(":"+port,nil)

Thefinalcodeshouldlooklikethis:

packagemain

import(

"net/http"

"os"

Deployment

Gettingsetup

ChangingtheCode

BuildingWebAppswithGo

11Deployment

Page 12: Building Web Apps With Go

"github.com/russross/blackfriday"

)

funcmain(){

port:=os.Getenv("PORT")

ifport==""{

port="8080"

}

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

http.ListenAndServe(":"+port,nil)

}

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

WeneedacouplesmallconfigurationfilestotellHerokuhowitshouldrunourapplication.ThefirstoneistheProcfile,whichallowsustodefinewhichprocessesshouldberunforourapplication.Bydefault,Gowillnametheexecutableafterthecontainingdirectoryofyourmainpackage.Forinstance,ifmywebapplicationlivedinGOPATH/github.com/codegangsta/bwag/deployment,myProcfilewilllooklikethis:

web:deployment

SpecificallytorunGoapplications,weneedtoalsospecifya.godirfiletotellHerokuwhichdirisinfactourpackagedirectory.

deployment

Onceallthesethingsinplace,Herokumakesiteasytodeploy.

InitializetheprojectasaGitrepository:

gitinit

gitadd-A

gitcommit-m"InitialCommit"

CreateyourHerokuapplication(specifyingtheGobuildpack):

herokucreate-bhttps://github.com/kr/heroku-buildpack-go.git

PushittoHerokuandwatchyourapplicationbedeployed!

gitpushherokumaster

Configuration

Deployment

BuildingWebAppswithGo

12Deployment

Page 13: Building Web Apps With Go

Viewyourapplicationinyourbrowser:

herokuopen

BuildingWebAppswithGo

13Deployment

Page 14: Building Web Apps With Go

Forsomesimpleapplications,thedefaulthttp.ServeMuxcantakeyouprettyfar.IfyouneedmorepowerinhowyouparseURLendpointsandroutethemtotheproperhandler,youmayneedtopullinathirdpartyroutingframework.Forthistutorial,wewillusethepopulargithub.com/julienschmidt/httprouterlibraryasourrouter.github.com/julienschmidt/httprouterisagreatchoiceforarouterasitisaverysimpleimplementationwithoneofthebestperformancebenchmarksoutofallthethirdpartyGorouters.

Inthisexample,wewillcreatesomeroutingforaRESTfulresourcecalled"posts".Belowwedefinemechanismstoviewindex,show,create,update,destroy,andeditposts.

packagemain

import(

"fmt"

"net/http"

"github.com/julienschmidt/httprouter"

)

funcmain(){

r:=httprouter.New()

r.GET("/",HomeHandler)

//Postscollection

r.GET("/posts",PostsIndexHandler)

r.POST("/posts",PostsCreateHandler)

//Postssingular

r.GET("/posts/:id",PostShowHandler)

r.PUT("/posts/:id",PostUpdateHandler)

r.GET("/posts/:id/edit",PostEditHandler)

fmt.Println("Startingserveron:8080")

http.ListenAndServe(":8080",r)

}

funcHomeHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"Home")

}

funcPostsIndexHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postsindex")

}

funcPostsCreateHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postscreate")

}

funcPostShowHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

id:=p.ByName("id")

fmt.Fprintln(rw,"showingpost",id)

}

funcPostUpdateHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postupdate")

}

funcPostDeleteHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postdelete")

}

funcPostEditHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postedit")

}

URLRouting

BuildingWebAppswithGo

14URLRouting

Page 15: Building Web Apps With Go

1. Explorethedocumentationforgithub.com/julienschmidt/httprouter.2. Findouthowwellgithub.com/julienschmidt/httprouterplaysnicelywithexistinghttp.Handlerslikehttp.FileServer3. httprouterhasaverysimpleinterface.Explorewhatkindofabstractionscanbebuiltontopofthisfastroutertomake

buildingthingslikeRESTfulroutingeasier.

Exercises

BuildingWebAppswithGo

15URLRouting

Page 16: Building Web Apps With Go

Ifyouhavesomecodethatneedstoberunforeveryrequest,regardlessoftheroutethatitwilleventuallyendupinvoking,youneedsomewaytostackhttp.Handlersontopofeachotherandruntheminsequence.Thisproblemissolvedelegantlythroughmiddlewarepackages.NegroniisapopularmiddlewarepackagethatmakesbuildingandstackingmiddlewareveryeasywhilekeepingthecomposablenatureoftheGowebecosystemintact.

NegronicomeswithsomedefaultmiddlewaresuchasLogging,ErrorRecovery,andStaticfileserving.SooutoftheboxNegroniwillprovideyouwithalotofvaluewithoutalotofoverhead.

TheexamplebelowshowshowtouseaNegronistackwiththebuiltinmiddlewareandhowtocreateyourowncustommiddleware.

packagemain

import(

"log"

"net/http"

"github.com/codegangsta/negroni"

)

funcmain(){

//Middlewarestack

n:=negroni.New(

negroni.NewRecovery(),

negroni.HandlerFunc(MyMiddleware),

negroni.NewLogger(),

negroni.NewStatic(http.Dir("public")),

)

n.Run(":8080")

}

funcMyMiddleware(rwhttp.ResponseWriter,r*http.Request,nexthttp.HandlerFunc){

log.Println("Loggingonthewaythere...")

ifr.URL.Query().Get("password")=="secret123"{

next(rw,r)

}else{

http.Error(rw,"NotAuthorized",401)

}

log.Println("Loggingonthewayback...")

}

1. ThinkofsomecoolmiddlewareideasandtrytoimplementthemusingNegroni.2. ExplorehowNegronicanbecomposedwithgithub.com/gorilla/muxusingthehttp.Handlerinterface.3. PlaywithcreatingNegronistacksforcertaingroupsofroutesinsteadoftheentireapplication.

Middleware

Exercises

BuildingWebAppswithGo

16Middleware

Page 17: Building Web Apps With Go

Renderingistheprocessoftakingdatafromyourapplicationordatabaseandpresentingitfortheclient.TheclientcanbeabrowserthatrendersHTML,oritcanbeanotherapplicationthatconsumesJSONasitsserializationformat.InthischapterwewilllearnhowtorenderbothoftheseformatsusingthemethodsthatGoprovidesforusinthestandardlibrary.

Rendering

BuildingWebAppswithGo

17Rendering

Page 18: Building Web Apps With Go

JSONisquicklybecomingtheubiquitousserializationformatforwebAPIs,soitmaybethemostrelevantwhenlearninghowtobuildwebappsusingGo.Fortunately,GomakesitsimpletoworkwithJSON--itisextremelyeasytoturnexistingGostructsintoJSONusingtheencoding/jsonpackagefromthestandardlibrary.

packagemain

import(

"encoding/json"

"net/http"

)

typeBookstruct{

Titlestring`json:"title"`

Authorstring`json:"author"`

}

funcmain(){

http.HandleFunc("/",ShowBooks)

http.ListenAndServe(":8080",nil)

}

funcShowBooks(whttp.ResponseWriter,r*http.Request){

book:=Book{"BuildingWebAppswithGo","JeremySaenz"}

js,err:=json.Marshal(book)

iferr!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

return

}

w.Header().Set("Content-Type","application/json")

w.Write(js)

}

1. ReadthroughtheJSONAPIdocsandfindouthowtorenameandignorefieldsforJSONserialization.2. Insteadofusingthejson.Marshalmethod,tryusingthejson.EncoderAPI.3. FigureourhowtoprettyprintJSONwiththeencoding/jsonpackage.

JSON

Exercises

BuildingWebAppswithGo

18JSON

Page 19: Building Web Apps With Go

ServingHTMLisanimportantjobforsomewebapplications.Gohasoneofmyfavoritetemplatinglanguagestodate.Notforitsfeatures,butforitssimplicityandoutoftheboxsecurity.RenderingHTMLtemplatesisalmostaseasyasrenderingJSONusingthe'html/template'packagefromthestandardlibrary.HereiswhatthesourcecodeforrenderingHTMLtemplateslookslike:

packagemain

import(

"html/template"

"net/http"

"path"

)

typeBookstruct{

Titlestring

Authorstring

}

funcmain(){

http.HandleFunc("/",ShowBooks)

http.ListenAndServe(":8080",nil)

}

funcShowBooks(whttp.ResponseWriter,r*http.Request){

book:=Book{"BuildingWebAppswithGo","JeremySaenz"}

fp:=path.Join("templates","index.html")

tmpl,err:=template.ParseFiles(fp)

iferr!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

return

}

iferr:=tmpl.Execute(w,book);err!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

}

}

Thisisthefollowingtemplatewewillbeusing.Itshouldbeplacedinatemplates/index.htmlfileinthedirectoryyourprogramisrunfrom:

<html>

<h1>{{.Title}}</h1>

<h3>by{{.Author}}</h3>

</html>

1. Lookthroughthedocsfortext/templateandhtml/templatepackage.Playwiththetemplatinglanguageabittogetafeelforitsgoals,strengths,andweaknesses.

2. Intheexampleweparsethefilesoneveryrequest,whichcanbealotofperformanceoverhead.Experimentwithparsingthefilesatthebeginningofyourprogramandexecutingtheminyourhttp.Handler(hint:makeuseoftheCopy()methodonhtml.Template).

3. Experimentwithparsingandusingmultipletemplates.

HTMLTemplates

Exercises

BuildingWebAppswithGo

19HTMLTemplates

Page 20: Building Web Apps With Go

IfyouwantrenderingJSONandHTMLtobeevensimpler,thereisthegithub.com/unrolled/renderpackage.Thispackagewasinspiredbythemartini-contrib/renderpackageandismygotowhenitcomestorenderingdataforpresentationinmywebapplications.

packagemain

import(

"net/http"

"gopkg.in/unrolled/render.v1"

)

funcmain(){

r:=render.New(render.Options{})

mux:=http.NewServeMux()

mux.HandleFunc("/",func(whttp.ResponseWriter,req*http.Request){

w.Write([]byte("Welcome,visitsubpagesnow."))

})

mux.HandleFunc("/data",func(whttp.ResponseWriter,req*http.Request){

r.Data(w,http.StatusOK,[]byte("Somebinarydatahere."))

})

mux.HandleFunc("/json",func(whttp.ResponseWriter,req*http.Request){

r.JSON(w,http.StatusOK,map[string]string{"hello":"json"})

})

mux.HandleFunc("/html",func(whttp.ResponseWriter,req*http.Request){

//Assumesyouhaveatemplatein./templatescalled"example.tmpl"

//$mkdir-ptemplates&&echo"<h1>Hello{{.}}.</h1>">templates/example.tmpl

r.HTML(w,http.StatusOK,"example",nil)

})

http.ListenAndServe(":8080",mux)

}

1. Havefunplayingwithalloftheoptionsavailablewhencallingrender.New()2. Tryusingthe.yieldhelperfunction(withthecurlybraces)andalayoutwithHTMLtemplates.

Usingtherenderpackage

Exercises

BuildingWebAppswithGo

20UsingTherenderpackage

Page 21: Building Web Apps With Go

Testingisanimportantpartofanyapplication.TherearetwoapproacheswecantaketotestingGowebapplications.Thefirstapproachisaunit-teststyleapproach.Theotherismoreofanend-to-endapproach.Inthischapterwe'llcoverbothapproaches.

Testing

BuildingWebAppswithGo

21Testing

Page 22: Building Web Apps With Go

Unittestingallowsustotestahttp.HandlerFuncdirectlywithoutrunninganymiddleware,routers,oranyothertypeofcodethatmightotherwisewrapthefunction.

packagemain

import(

"fmt"

"net/http"

)

funcHelloWorld(reshttp.ResponseWriter,req*http.Request){

fmt.Fprint(res,"HelloWorld")

}

funcmain(){

http.HandleFunc("/",HelloWorld)

http.ListenAndServe(":3000",nil)

}

Thisisthetestfile.Itshouldbeplacedinthesamedirectoryasyourapplicationandnamemain_test.go.

packagemain

import(

"net/http"

"net/http/httptest"

"testing"

)

funcTest_HelloWorld(t*testing.T){

req,err:=http.NewRequest("GET","http://example.com/foo",nil)

iferr!=nil{

t.Fatal(err)

}

res:=httptest.NewRecorder()

HelloWorld(res,req)

exp:="HelloWorld"

act:=res.Body.String()

ifexp!=act{

t.Fatalf("Expected%sgog%s",exp,act)

}

}

1. ChangetheoutputofHelloWorldtoprintaparameterandthentestthattheparameterisrendered.2. CreateaPOSTrequestandtestthattherequestisproperlyhandled.

UnitTesting

Exercises

BuildingWebAppswithGo

22UnitTesting

Page 23: Building Web Apps With Go

Endtoendallowsustotestapplicationsthroughthewholerequestcycle.Whereunittestingismeanttojusttestaparticularfunction,endtoendtestswillrunthemiddleware,router,andotherthatarequestmypassthrough.

packagemain

import(

"fmt"

"net/http"

"github.com/codegangsta/negroni"

"github.com/julienschmidt/httprouter"

)

funcHelloWorld(reshttp.ResponseWriter,req*http.Request,phttprouter.Params){

fmt.Fprint(res,"HelloWorld")

}

funcApp()http.Handler{

n:=negroni.Classic()

m:=func(reshttp.ResponseWriter,req*http.Request,nexthttp.HandlerFunc){

fmt.Fprint(res,"Before...")

next(res,req)

fmt.Fprint(res,"...After")

}

n.Use(negroni.HandlerFunc(m))

r:=httprouter.New()

r.GET("/",HelloWorld)

n.UseHandler(r)

returnn

}

funcmain(){

http.ListenAndServe(":3000",App())

}

Thisisthetestfile.Itshouldbeplacedinthesamedirectoryasyourapplicationandnamemain_test.go.

packagemain

import(

"io/ioutil"

"net/http"

"net/http/httptest"

"testing"

)

funcTest_App(t*testing.T){

ts:=httptest.NewServer(App())

deferts.Close()

res,err:=http.Get(ts.URL)

iferr!=nil{

t.Fatal(err)

}

body,err:=ioutil.ReadAll(res.Body)

res.Body.Close()

iferr!=nil{

t.Fatal(err)

}

exp:="Before...HelloWorld...After"

EndToEndTesting

BuildingWebAppswithGo

23EndtoEndTesting

Page 24: Building Web Apps With Go

ifexp!=string(body){

t.Fatalf("Expected%sgot%s",exp,body)

}

}

1. Createanotherpieceofmiddlewarethatmutatesthestatusoftherequest.2. CreateaPOSTrequestandtestthattherequestisproperlyhandled.

Exercises

BuildingWebAppswithGo

24EndtoEndTesting

Page 25: Building Web Apps With Go

Controllersareafairlyfamiliartopicinotherwebdevelopmentcommunities.Sincemostwebdevelopersrallyaroundthemightynet/httpinterface,notmanycontrollerimplementationshavecaughtonstrongly.However,thereisgreatbenefitinusingacontrollermodel.Itallowsforclean,welldefinedabstractionsaboveandbeyondwhatthenet/httphandlerinterfacecanaloneprovide.

InthisexamplewewillexperimentwithbuildingourowncontrollerimplementationusingsomestandardfeaturesinGo.Butfirst,letsstartwiththeproblemswearetryingtosolve.Sayweareusingtherenderlibrarythatwetalkedaboutinpreviouschapters:

varRender=render.New(render.Options{})

Ifwewantourhttp.Handlerstobeableaccessourrender.Renderinstance,wehaveacoupleoptions.

1.Useaglobalvariable:Thisisn'ttoobadforsmallprograms,butwhentheprogramgetslargeritquicklybecomesamaintenancenightmare.

2.Passthevariablethroughaclosuretothehttp.Handler:Thisisagreatidea,andweshouldbeusingitmostofthetime.Theimplementationendsuplookinglikethis:

funcMyHandler(r*render.Render)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

//nowwecanaccessr

})

}

Whenyourprogramgrowsinsize,youwillstarttonoticethatmanyofyourhttp.Handlerswillsharethesamedependenciesandyouwillhavealotoftheseclosurizedhttp.Handlerswiththesamearguments.ThewayIliketocleanthisupistowritealittlebasecontrollerimplementationthataffordsmeafewwins:

1. Allowsmetosharethedependenciesacrosshttp.Handlersthathavesimilargoalsorconcepts.2. Avoidsglobalvariablesandfunctionsforeasytesting/mocking.3. GivesmeamorecentralizedandGo-likemechanismforhandlingerrors.

Thegreatpartaboutcontrollersisthatitgivesusallthesethingswithoutimportinganexternalpackage!MostofthisfunctionalitycomesfromcleveruseoftheGofeatureset,namelyGostructsandembedding.Let'stakealookattheimplementation.

packagemain

import"net/http"

//Actiondefinesastandardfunctionsignatureforustousewhencreating

//controlleractions.Acontrolleractionisbasicallyjustamethodattachedto

//acontroller.

typeActionfunc(rwhttp.ResponseWriter,r*http.Request)error

Controllers

HandlerDependencies

CaseforControllers

BuildingWebAppswithGo

25Controllers

Page 26: Building Web Apps With Go

//ThisisourBaseController

typeAppControllerstruct{}

//Theactionfunctionhelpswitherrorhandlinginacontroller

func(c*AppController)Action(aAction)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

iferr:=a(rw,r);err!=nil{

http.Error(rw,err.Error(),500)

}

})

}

Thatsit!Thatisalltheimplementationthatweneedtohavethepowerofcontrollersatourfingertips.Allwehavelefttodoisimplementanexamplecontroller:

packagemain

import(

"net/http"

"gopkg.in/unrolled/render.v1"

)

typeMyControllerstruct{

AppController

*render.Render

}

func(c*MyController)Index(rwhttp.ResponseWriter,r*http.Request)error{

c.JSON(rw,200,map[string]string{"Hello":"JSON"})

returnnil

}

funcmain(){

c:=&MyController{Render:render.New(render.Options{})}

http.ListenAndServe(":8080",c.Action(c.Index))

}

1. ExtendMyControllertohavemultipleactionsfordifferentroutesinyourapplication.2. Playwithmorecontrollerimplementations,getcreative.3. OverridetheActionmethodonMyControllertorenderaerrorHTMLpage.

Exercises

BuildingWebAppswithGo

26Controllers

Page 27: Building Web Apps With Go

OneofthemostaskedquestionsIgetaboutwebdevelopmentinGoishowtoconnecttoaSQLdatabase.Thankfully,GohasafantasticSQLpackageinthestandardlibrarythatallowsustouseawholeslewofdriversfordifferentSQLdatabases.InthisexamplewewillconnecttoaSQLitedatabase,butthesyntax(minussomesmallSQLsemantics)isthesameforaMySQLorPostgreSQLdatabase.

packagemain

import(

"database/sql"

"fmt"

"log"

"net/http"

_"github.com/mattn/go-sqlite3"

)

funcmain(){

db:=NewDB()

log.Println("Listeningon:8080")

http.ListenAndServe(":8080",ShowBooks(db))

}

funcShowBooks(db*sql.DB)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

vartitle,authorstring

err:=db.QueryRow("selecttitle,authorfrombooks").Scan(&title,&author)

iferr!=nil{

panic(err)

}

fmt.Fprintf(rw,"Thefirstbookis'%s'by'%s'",title,author)

})

}

funcNewDB()*sql.DB{

db,err:=sql.Open("sqlite3","example.sqlite")

iferr!=nil{

panic(err)

}

_,err=db.Exec("createtableifnotexistsbooks(titletext,authortext)")

iferr!=nil{

panic(err)

}

returndb

}

1. MakeuseoftheQueryfunctiononoursql.DBinstancetoextractacollectionofrowsandmapthemtostructs.2. AddtheabilitytoinsertnewrecordsintoourdatabasebyusinganHTMLform.3. gogetgithub.com/jmoiron/sqlxandobservetheimprovementsmadeovertheexistingdatabase/sqlpackageinthe

standardlibrary.

Databases

Exercises

BuildingWebAppswithGo

27Databases

Page 28: Building Web Apps With Go

Sometimesyouwanttopassdatatoahttp.HandlerFunconinitialization.Thiscaneasilybedonebycreatingaclosureofthehttp.HandlerFunc:

funcMyHandler(database*sql.DB)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

//younowhaveaccesstothe*sql.DBhere

})

}

ItisprettyoftenthatweneedtostoreandretrievedatathatisspecifictothecurrentHTTPrequest.Usegorilla/contexttomapvaluesandretrievethemlater.Itcontainsaglobalmutexonamapofrequestobjects.

funcMyHandler(whttp.ResponseWriter,r*http.Request){

val:=context.Get(r,"myKey")

//returns("bar",true)

val,ok:=context.GetOk(r,"myKey")

//...

}

TipsandTricks

Wrapahttp.HandlerFuncclosure

Usinggorilla/contextforrequest-specificdata

BuildingWebAppswithGo

28TipsandTricks

Page 29: Building Web Apps With Go

You'vedoneit!YouhavegottenatasteofGowebdevelopmenttoolsandlibraries.Atthetimeofthiswriting,thisbookisstillinflux.ThissectionisreservedformoreGowebresourcestocontinueyourlearning.

MovingForward

BuildingWebAppswithGo

29MovingForward