+ All Categories
Home > Documents > Building Web Apps With Go

Building Web Apps With Go

Date post: 01-Feb-2016
Category:
Upload: dev-crc
View: 8 times
Download: 0 times
Share this document with a friend
Description:
laravel5essencial
Popular Tags:
29
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


Recommended