1.1
1.2
1.2.1
1.2.2
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.3.5
1.3.6
1.3.7
1.4
1.4.1
1.4.2
1.5
1.5.1
1.5.2
1.5.3
1.5.4
1.6
1.6.1
1.6.2
1.7
1.7.1
1.8
1.9
1.10
1.11
1.12
TableofContentsIntroduction
InstallationandTools
Installation
Tools
Gobasicknowledge
Gofoundation
Controlstatementsandfunctions
struct
Object-oriented
interface
Concurrency
Summary
General
GoProgrammingBasics
WebProgrammingBasics
Implementation
Basicwebapplication
Designingourwebapp
DatabaseHandling
WebappExample
Formhandling
WorkingwithForms
UploadingFiles
Templates
BasicsofTemplates
UserAuthentication
WorkingwithFiles
Routing
Middleware
BuildinganAPI
2
1.13Contributors
3
Aboutthebook
ThisbookwaswrittentoteachhowtodevelopwebapplicationsinGofornewbies.
Youwillcreateatodolistapplicationasyougoaheadinthebook.Thebookaimstoteachconceptsbasedonsimpleyetrealexamplesandnotsomedummyexamples.
ThereisacodesectionavailableintheGithubrepo.Ifyouarereadingsomewhereotherthangithub,here'sthelinktotheGithubRepo
ReadOnlineDownloadPDF
CodeThebookcomeswithcorrespondingcode,pleaseuseittounderstandthebookcompletely,thebookisjustplainoldtheoryifyoudonotcheckthecodeout.
ContributingIdon'tprofesstobeaGodofeitherGoorwebdevoranythingingeneral,andIdon'tclaimthatthisisthebestbookforlearninghowtobuildwebappplicationswithGo,butIdobelievethatgoodthingshappenwhenpeoplecollaborate,sopullrequestsarenotonlyappreciated,buttheyarewelcome.
Igotfeedbackfromareddituserthatmaybeitistooearlyformetostartwritingthisbook,decadesago,ayoungstudentfromtheUniversityofHelsinkihadanendlessdebatewithAndrewTannenbaumovercomp.minix,itwasaboutmonolithickernels,hadthestudentlistenedtoAndrewTannenbaum,theworldprobablywouldnothavehadLinux.Thisisthewholepointofopensourceprojects,alittleinitiativefromeveryonegoesalongway.
PhilosophyThroughthisbookwewanttoteachhowtodevelopwebapplicationsinGo.WeexpectthereadertoknowthebasicsofGobutweassumethereaderknowsnothingabouthowtowritewebapplicationsThebookshallcompriseofchapters,ifthetopicishugeanddoesn'tfitintoonechapter,thenwesplitintomultiplechapters,ifpossible.
Introduction
4
Eachchaptershouldbesplitintologicalpartsorsectionswithameaningfultitlewhich'llteachthereadersomething.EveryconceptshouldbeaccompaniedbytheGocode(ifthereisany),forsneakpeektypesectionswritetheGopseudocode,writingjustthenecessarypartsofthecodeandkeepingeverythingelse.Thecodeshouldn'tbemorethan80characterswidebecauseinthePDFversionsofthebookthecodeisinvisible.Brevityisthesoulofwit,sokeepthedescriptionassmallaspossible.Butthisdoesn'tmeanthatweshouldjustassumethatthereaderknowstheconceptandskipitinsuchcasesdoexplaintheconcept.Inthetodolistmanagerwhichwearecreating,we'llstrivetoimplementasmuchfunctionalityaspossibletogiveatasteofpracticalGoprogrammingtothereader,butweshouldmentionasanotetheotherway,supposeyoure-implementafunctionlikeParseGlobbylistingallhtmlfilesandusingParseFilestoparsethem,weshouldmentionaboutthefunctionParseGlobThemaintitleshouldhaveone#,sectionsshouldhave3#'snoteshouldhave6#'s(noteshouldhaveatitletoo)Multilinecodeshouldhavethreetabsindentation,singlelineofcodecanbeindentedusingtabsorbybackticks.
WrittenwithloveinIndia.
License:BookLicense:CCBY-SA3.0License
Note:1. TheGoProgrammingBasicssectionhasbeenadaptedfrombuild-web-application-with-
golangbyastaxieLinkswereupdatedtoreferthecorrectaspectsofthecurrentbook,titleswereupdatedtofitintothisbook.
2. Thegopherinthecoverpageistakenfromhttps://golang.org/doc/gopher/appenginegophercolor.jpgwithoutmodifications.
LinksNextsection:InstallationandTools
Introduction
5
Introduction
6
InstallationIfyouknowaboutinstallationorhaveinstalledGo,youcanskiptoTools.
Thischapteristakenfrominstallpageverbatim,exceptforthechangestobemadetoadapttothisbook'sstylingformat.
Systemrequirements
Gobinarydistributionsareavailableforthesesupportedoperatingsystemsandarchitectures.Pleaseensureyoursystemmeetstheserequirementsbeforeproceeding.IfyourOSorarchitectureisnotonthelist,youmaybeabletoinstallfromsourceorusegccgoinstead
Operatingsystem Architectures Notes
FreeBSD8-STABLEorlater amd64 DebianGNU/kFreeBSDnotsupported
Linux2.6.23orlaterwithglibc
amd64,386,arm
CentOS/RHEL5.xnotsupported;installfromsourceforARM
MacOSX10.7orlater amd64 usetheclangorgcc†thatcomeswith
Xcode‡
WindowsXPorlater amd64,386 useMinGWgcc†.Noneedforcygwinormsys
†gccisrequiredonlyifyouplantousecgo.
‡YouonlyneedtoinstallthecommandlinetoolsforXcode.IfyouhavealreadyinstalledXcode4.3+,youcaninstallitfromtheComponentstaboftheDownloadspreferencespanel.
InstalltheGotoolsIfyouareupgradingfromanolderversionofGoyoumustfirstremovetheexistingversion.Linux,MacOSX,andFreeBSDtarballs
Downloadthearchiveandextractitinto/usr/local,creatingaGotreein/usr/local/go.Forexample:
tar-C/usr/local-xzfgo$VERSION.$OS-$ARCH.tar.gz
InstallationandTools
7
Choosethearchivefileappropriateforyourinstallation.Forinstance,ifyouareinstallingGoversion1.2.1for64-bitx86onLinux,thearchiveyouwantiscalledgo1.2.1.linux-amd64.tar.gz.
(Typicallythesecommandsmustberunasrootorthroughsudo.)
Add/usr/local/go/bintothePATHenvironmentvariable.Youcandothisbyaddingthislinetoyour/etc/profile(forasystem-wideinstallation)or$HOME/.profile:
exportPATH=$PATH:/usr/local/go/bin
Installingtoacustomlocation
TheGobinarydistributionsassumetheywillbeinstalledin/usr/local/go(orc:\GounderWindows),butitispossibletoinstalltheGotoolstoadifferentlocation.InthiscaseyoumustsettheGOROOTenvironmentvariabletopointtothedirectoryinwhichitwasinstalled.
Forexample,ifyouinstalledGotoyourhomedirectoryyoushouldaddthefollowingcommandsto$HOME/.profile:
exportGOROOT=$HOME/go
exportPATH=$PATH:$GOROOT/bin
Note:GOROOTmustbesetonlywheninstallingtoacustomlocation.
MacOSXpackageinstaller
Downloadthepackagefile,openit,andfollowthepromptstoinstalltheGotools.ThepackageinstallstheGodistributionto/usr/local/go.
Thepackageshouldputthe/usr/local/go/bindirectoryinyourPATHenvironmentvariable.YoumayneedtorestartanyopenTerminalsessionsforthechangetotakeeffect.Windows
TheGoprojectprovidestwoinstallationoptionsforWindowsusers(besidesinstallingfromsource):aziparchivethatrequiresyoutosetsomeenvironmentvariablesandanMSIinstallerthatconfiguresyourinstallationautomatically.MSIinstaller
OpentheMSIfileandfollowthepromptstoinstalltheGotools.Bydefault,theinstallerputstheGodistributioninc:\Go.
Theinstallershouldputthec:\Go\bindirectoryinyourPATHenvironmentvariable.Youmayneedtorestartanyopencommandpromptsforthechangetotakeeffect.Ziparchive
Downloadthezipfileandextractitintothedirectoryofyourchoice(wesuggestc:\Go).
InstallationandTools
8
Ifyouchoseadirectoryotherthanc:\Go,youmustsettheGOROOTenvironmentvariabletoyourchosenpath.
AddthebinsubdirectoryofyourGoroot(forexample,c:\Go\bin)toyourPATHenvironmentvariable.SettingenvironmentvariablesunderWindows
UnderWindows,youmaysetenvironmentvariablesthroughthe"EnvironmentVariables"buttononthe"Advanced"tabofthe"System"controlpanel.SomeversionsofWindowsprovidethiscontrolpanelthroughthe"AdvancedSystemSettings"optioninsidethe"System"controlpanel.Testyourinstallation
CheckthatGoisinstalledcorrectlybysettingupaworkspaceandbuildingasimpleprogram,asfollows.
Createadirectorytocontainyourworkspace,$HOME/workforexample,andsettheGOPATHenvironmentvariabletopointtothatlocation.
$exportGOPATH=$HOME/work
Youshouldputtheabovecommandinyourshellstartupscript($HOME/.profileforexample)or,ifyouuseWindows,followtheinstructionsabovetosettheGOPATHenvironmentvariableonyoursystem.
Next,makethedirectoriessrc/github.com/user/helloinsideyourworkspace(ifyouuseGitHub,substituteyourusernameforuser),andinsidethehellodirectorycreateafilenamedhello.gowiththefollowingcontents:
packagemain
import"fmt"
funcmain(){
fmt.Printf("hello,world\n")
}
Thencompileitwiththegotool:
$goinstallgithub.com/user/hello
Theabovecommandwillputanexecutablecommandnamedhello(orhello.exe)insidethebindirectoryofyourworkspace.Executethecommandtoseethegreeting:
$$GOPATH/bin/hello
InstallationandTools
9
hello,world
Ifyouseethe"hello,world"messagethenyourGoinstallationisworking.
BeforerushingofftowriteGocodepleasereadtheHowtoWriteGoCodedocument,whichdescribessomeessentialconceptsaboutusingtheGotools.UninstallingGo
ToremoveanexistingGoinstallationfromyoursystemdeletethegodirectory.Thisisusually/usr/local/gounderLinux,MacOSX,andFreeBSDorc:\GounderWindows.
YoushouldalsoremovetheGobindirectoryfromyourPATHenvironmentvariable.UnderLinuxandFreeBSDyoushouldedit/etc/profileor$HOME/.profile.IfyouinstalledGowiththeMacOSXpackagethenyoushouldremovethe/etc/paths.d/gofile.WindowsusersshouldreadthesectionaboutsettingenvironmentvariablesunderWindows.Gettinghelp
Forreal-timehelp,askthehelpfulgophersin#go-nutsontheFreenodeIRCserver.
TheofficialmailinglistfordiscussionoftheGolanguageisGoNuts.
ReportbugsusingtheGoissuetracker.
InstallationandTools
10
ToolsForhtml:Brackets,atexteditorforthewebbyAdobe.
ForGo:AnyIDEofyourchoicewhichhasaGolanguageplugin.
gofmt
Usage:
gofmt<filename>:printstheformattedsourcecodeontheconsole
gofmt-w<filename/foldername>:writestheformattedcodeinsidethefile(s).
gofmtformatsthesourcecodeofaGosourcefile/filesinafolder.ThebasicpointbehindtheGolanguageisthattheyhavestandardizedformatting.Thelanguageauthorswantedtocreatealanguagethatgetsthingsdonequicklyanddidn'twanttheusersofthelanguagetowageintheendlessdebateontrivialissueslikethecodeformatting.MostIDEscanbeconfiguredtorungofmtonsave.Itisrecommendedtorungofmtbeforecommittingtoversioncontrol.
godoc
DocumentationinGo,isdoneviacomments,eachexportedfunction/variableissupposedtohaverespectivecommentsonwhatitdoes.Thecommandgodocisthestandarddocumentationgenerationtool.ItextractsdocumentationcommentsonalltheGoprojectspresentin$GOPATH/src.It'sagoodpracticetogiveproperdocumentationwhileprogramming,tomakethecodeeasiertounderstandfornewcomers,forclosedsourceandopensourceprojectsalike.
Note:
godoc,bydefaultrunsontheentire$GOPATH,sodependingontheprojectsyouhaveinyour$GOPATH,itmighttakefromfewsecondstofewminutesforgodoctostarttheserver,godocdoesn'tnotifyyouwhentheserverhasstarted,theydohaveaverboseflagwhichprintswhentheserverhasstarted.Itis-v
Therearetwomodesforgodoc,
Webinterface:Usage:godoc-http=:6060-v
Forseeingdocumentationofnet/httpthelinkislocalhost:6060/pkg/net/http
Tools
11
CommandlineinterfaceUsage:godocnet/http
Thiswillprovidethedocumentationofnet/http
gotest
ThisisthetestingtoolchainforGo.Foreachfile.go,thecorrespondingtestcasesshouldbepresentinafilenamedasfile_test.go.Ifmain.goisourGosourcefile,weshouldmakemain_test.gofileinthesamefolderasmain.go.TheGocompilerignoresallthe_test.gofiles.
gobuild
Weusethiscommandtodotobuildourapplication.Itparsesallthe.gofilesexceptthe_test.gofilesintheentirefolderandallsubfoldersalongwithimportedlibrariesifany,andcreatesastaticallylinkedbinary.Thebinarynameisthesameastheprojectfoldername,ifwewantacustomnameweshouldusethe-oflag.
Example:gobuild-otasks
NoteCrosscompilation
WithGo,youcancrosscompileyourapplication.BelowisthecodetocompiletheapplicationforWindowsandMacfromLinux.
envGOOS=darwinGOARCH=386gobuild-otasks.app
envGOOS=windowsGOARCH=386gobuild-otasks.exe
Iftherearenoquirksonthelibraries,shouldgiveyouabinaryfortherespectiveplatforms.
goinstall
Thiscommandcreatesastaticallylinkedbinaryandplacesitin$GOPATH/binfolder.Italsocreatesabinaryversionofallthedependentlibrariesandputsitinthe$GOPATH/pkgfolderintherespectivedirectories.
Note
Whilebuildingyourapplication,firstusegoinstallwhich'llcacheyourdependentlibraries.Thenforsubsequentchangesusegobuild,thiswillsavealotofbuildtime.Thisisbecausegobuilddoesn'tcacheanyresults,itbuildseverything.
gorun
Tools
12
Whilerunningyourappthroughthecommandlineyoutypicallhavetodothefollowing:gobuild-oapp./app
goruncombinesthemintoonecommand,itgeneratesandrunsabinaryofyourproject.Thebinaryfile,howeverisn'tretainedaftertherun.
goget
ThisisusedtoinstallpackagesinGo.Itinternallyclonestheversioncontrolrepositoryparameterpassedtoit,canbeanylocal/remotegitrepository.Itthenrunsgoinstallonthelibrary,makingthelibraryavailablein$GOPATH/pkg.
Tools
13
WhatmakesGodifferentfromotherlanguages?TheGoprogramminglanguagewascreatedforwritinglargescalesoftwareeffectively.Thisisthereasonthelanguagehasstrictusageguidelines.InClikelanguagestherearetwofactionsofprogrammers,
thosewhodothis
publicstaticvoidmain(){
}
andthosewhodothis
publicstaticvoidmain()
{
}
ThesamecanbesaidofPythonfourspaces/tabs.
Thismightseemtobeashallowproblematthetop,butwhenthecodebaseandteamsizegrows,thenitisdifficulttomaintainthecode's"beauty"becauseofdifferentuserpreferences.Anyonecanwritecodethesedays,eitherbythemselvesorbycopyingovertheInternet,weshouldstrivetowriteelegantcode,withasmanycommentsaspossible.PythonhasPEP8,whichwascreatedtobringinsomedisciplineforformattingandnamingconventions.
GowasbuiltatGoogle,ascompanyweknowasasynonymforSearchandDistributedComputing,withtheirBorg.Theywantedalanguagethatwasfast,workedwellwithautomatedcodereviewandformattingandallowedalargeteamtowritelargescalesoftwareeffectively.Theydidn'twantusersofGotogetinvolvedintheneverendingtrivialwarsof4spacesvs1tab,hencetherearemanyrestrictionsonthelanguage.Alsoallthemajorlanguagearedecadesold,theywerecreatedinatimewherememorywascostly,thusconcurrencywasn'taproblemfortheircreators.SinceIntelIsraelcameupwiththemulticoreidea,allprocessorchipsaremulticores,Gowasdesignedwithconcurrencyinmind.AsRobPike,inhisamazingtalksaid,Concurrencyisn'tparallelism.
1. Unusedimports/variablesarecompilererrors2. Noneedtoputsemicolon'sbecausethecompileritselfwilladdsemicolonsattheend
Gobasicknowledge
14
ofline,thisisthereasonyoucan'twritefunctionslikethesecondway,wementionedintheabovesection
3. AllyourGocodeispresentinasinglefolder,$GOPATH,itiscalled.Saygoodbyetoyourcodethrownallaroundinyourmachine
4. gofmtwillformatyourcode,sothereisonestandardwaytowriteGocode.5. Builtinhttp/testingsupport6. Compiledlanguage,thusveryfast.7. Canwritewebappswithoutanyframeworks.
2.1Hello,GoLet'sstartwithasimpleexample,thecustomaryHelloWorld.
Program
packagemain
import"fmt"
funcmain(){
fmt.Printf("Hello,worldor你好,世界orκαλημρακóσμorこんにちは世界\n")
}
Itprintsfollowinginformation.
Hello,worldor你好,世界orκαλημρακóσμorこんにちは世界
ExplanationGoprogramsusepackages,whicharesameaslibrariesinotherlanguages.MainisaspecialpackageinGo,whenthecompilerstartscompilingthesourcecode,itstartswiththemainpackage.
package<pkgName>(Inthiscaseispackagemain)tellsusthissourcefilebelongstomainpackage,andthekeywordmaintellsusthispackagewillbecompiledtoaprograminsteadofpackagefileswhoseextensionsare.a.
Gobasicknowledge
15
Perexecutableprogram,therecanbeonlyonemainpackage,andonemainfunctionwithnoargumentspassedorreturned.ThePrintffunction,isimportedfromtheformatpackagecalledfmt,weimportapackageusingimport"fmt".Whencallingafunctionnameorreferringtoavariableinsideanotherpackage,weusepkgName.FunctionNamelikefmt.Println().
Also,Gosupportsmultiplereturnvalues!
Intheexample,weprintednonASCIIcharacters.GosupportsUTF-8bydefault.
Themainpackage
EveryGoprogramshouldbeinapackage,itcanbeeitherMainoranyotherpackage.Eachpackageotherthanmainshouldbepresentasadistinctfolderinto$GOPATH.Thismeansthatyoucandirectlycreateamain.gofilewithpackagemainatit'sstartwithoutcreatingamainfolderandamain.gofileinsideit.
Careneedstobetakenwhilebuilding/runningtheapplication.
Withmainfolder:
[Tasks]$gobuildmain/main.go
[Tasks]$./main/main
Thiswillfunctioncorrectly,becauseweareintheTasksdirectorywhileexecutingourbinary,allthetemplatesandotherfilesarepresentinthisfolder.
Withoutthemainfolder
[Tasks/main]$gobuildmain.go
[Tasks/main]$./main
Here,weareintheTasks/maindirectory,thebinarywillexpectalltheotherfilesintheTasks/maindirectorywhentheyareintheTasksdirectory,
Gobasicknowledge
16
2.2Gofoundation
DefinevariablesWeusethekeywordvartodefineavariable.NotethatinGothevariabletypecomesafterthevariablename.
//defineavariablewithname“variableName”andtype"type"
varvariableNametype
//definethreevariableswhichtypesare"type"
varvname1,vname2,vname3type
//defineavariablewithname“variableName”,type"type"andvalue"value"
varvariableNametype=value
/*
Definethreevariableswithtype"type",andinitializetheirvalues.
vname1isv1,vname2isv2,vname3isv3
*/
varvname1,vname2,vname3type=v1,v2,v3
Thereisashortcutmethodtodeclarevariables:
/*
Definethreevariableswithouttype"type"andwithoutkeyword"var",andinitializet
heirvalues.
vname1isv1,vname2isv2,vname3isv3
*/
vname1,vname2,vname3:=v1,v2,v3
:=canonlybeusedinsidefunctions,fordefiningglobalvariableswehavetosticktousingvar.
_variableiscalledtheblankvariableanditisusedtoignoreavalue.Thisisauselessexample,butwe'llseeitsexamplesoonenough.
_,b:=34,35//usetheblankoperatortothrowawayavalue
Unusedvariablescausecompilationerrors.Compilethefollowingcodeandseewhathappens.
Gofoundation
17
packagemain
funcmain(){
variint
}
ConstantsConstantsarethevaluesthataredeterminedduringcompiletimeandyoucannotchangethemduringruntime.InGo,youcanusenumber,booleanorstringastypesofconstants.
Defineconstantsasfollows.
constconstantName=value
//youcanassigntypeofconstantsifit'snecessary
constPifloat32=3.1415926
Moreexamples.
constPi=3.1415926
consti=10000
constMaxThread=10
constprefix="astaxie_"
Elementarytypes
Boolean
Weusebooltodefineavariableasbooleantype,thevaluecanonlybetrueorfalse,andfalsewillbethedefaultvalue.(Youcannotconvertvariables'typebetweennumberandboolean!)
//samplecode
varisActivebool//globalvariable
varenabled,disabled=true,false//omittypeofvariables
functest(){
varavailablebool//localvariable
valid:=false//briefstatementofvariable
available=true//assignvaluetovariable
}
Gofoundation
18
Numericaltypes
Integertypesincludebothsignedandunsignedintegertypes.Gohasintanduintatthesametime,theyhavesamelength,butspecificlengthdependsonyouroperatingsystem.Theyuse32-bitin32-bitoperatingsystems,and64-bitin64-bitoperatingsystems.Goalsohastypesthathavespecificlengthincludingrune,int8,int16,int32,int64,byte,uint8,uint16,uint32,uint64.Notethatruneisaliasofint32andbyteisaliasofuint8.
Oneimportantthingyoushouldknowthatyoucannotassignvaluesbetweenthesetypes,thisoperationwillcausecompileerrors.
varaint8
varbint32
c:=a+b
Althoughint32hasalongerlengththanint8,andhasthesametypeasint,youcannotassignvaluesbetweenthem.(cwillbeassertedastypeinthere)
Floattypeshavethefloat32andfloat64typesandnotypecalledfloat.Thelatteroneisthedefaulttypeifusingbriefstatement.
Gosupportscomplexnumbersaswell.complex128(witha64-bitrealand64-bitimaginarypart)isthedefaulttype,ifyouneedasmallertype,thereisonecalledcomplex64(witha32-bitrealand32-bitimaginarypart).ItsformisRE+IMi,whereREisrealpartandIMisimaginarypart,thelastiistheimaginarynumber.Thereisaexampleofcomplexnumber.
varccomplex64=5+5i
//output:(5+5i)
fmt.Printf("Valueis:%v",c)
String
WejusttalkedabouthowGousestheUTF-8characterset.Stringsarerepresentedbydoublequotes""orbackticks `.
Gofoundation
19
//samplecode
varfrenchHellostring//basicformtodefinestring
varemptyStringstring=""//defineastringwithemptystring
functest(){
no,yes,maybe:="no","yes","maybe"//briefstatement
japaneseHello:="Ohaiou"
frenchHello="Bonjour"//basicformofassignvalues
}
It'simpossibletochangestringvaluesbyindex.Youwillgeterrorswhenyoucompilethefollowingcode.
varsstring="hello"
s[0]='c'
WhatifIreallywanttochangejustonecharacterinastring?Trythefollowingcode.
s:="hello"
c:=[]byte(s)//convertstringto[]bytetype
c[0]='c'
s2:=string(c)//convertbacktostringtype
fmt.Printf("%s\n",s2)
Youusethe+operatortocombinetwostrings.
s:="hello,"
m:="world"
a:=s+m
fmt.Printf("%s\n",a)
andalso.
s:="hello"
s="c"+s[1:]//youcannotchangestringvaluesbyindex,butyoucangetvaluesin
stead.
fmt.Printf("%s\n",s)
WhatifIwanttohaveamultiple-linestring?
m:=`hello
world`
willnotescapeanycharactersinastring.
Gofoundation
20
Errortypes
Gohasoneerrortypeforpurposeofdealingwitherrormessages.Thereisalsoapackagecallederrorstohandleerrors.
err:=errors.New("emitmachodwarf:elfheadercorrupted")
iferr!=nil{
fmt.Print(err)
}
Underlyingdatastructure
ThefollowingpicturecomesfromanarticleaboutGodatastructureinRussCox'sBlog.Asyoucansee,Goutilizesblocksofmemorytostoredata.
Figure2.1Gounderlyingdatastructure
Someskills
Definebygroup
Ifyouwanttodefinemultipleconstants,variablesorimportpackages,youcanusethegroupform.
Basicform.
import"fmt"
import"os"
consti=100
constpi=3.1415
constprefix="Go_"
variint
varpifloat32
varprefixstring
Groupform.
Gofoundation
21
import(
"fmt"
"os"
)
const(
i=100
pi=3.1415
prefix="Go_"
)
var(
iint
pifloat32
prefixstring
)
Unlessyouassignthevalueofconstantisiota,thefirstvalueofconstantinthegroupconst()willbe0.Iffollowingconstantsdon'tassignvaluesexplicitly,theirvalueswillbethesameasthelastone.Ifthevalueoflastconstantisiota,thevaluesoffollowingconstantswhicharenotassignedareiotaalso.
iotaenumerate
Gohasonekeywordcallediota,thiskeywordistomakeenum,itbeginswith0,increasedby1.
const(
x=iota//x==0
y=iota//y==1
z=iota//z==2
w//Ifthereisnoexpressionaftertheconstantsname,itusesthelastexpress
ion,
//soit'ssayingw=iotaimplicitly.Thereforew==3,andyandzbothcanomit
"=iota"aswell.
)
constv=iota//onceiotameetskeyword`const`,itresetsto`0`,sov=0.
const(
e,f,g=iota,iota,iota//e=0,f=0,g=0valuesofiotaaresameinoneline.
)
Somerules
ThereasonthatGoisconcisebecauseithassomedefaultbehaviors.
Gofoundation
22
Anyvariablethatbeginswithacapitallettermeansitwillbeexported,privateotherwise.Thesameruleappliesforfunctionsandconstants,nopublicorprivatekeywordexistsinGo.
array,slice,map
array
arrayisanarrayobviously,wedefineoneasfollows.
vararr[n]type
in[n]type,nisthelengthofthearray,typeisthetypeofitselements.Likeotherlanguages,weuse[]togetorsetelementvalueswithinarrays.
vararr[10]int//anarrayoftype[10]int
arr[0]=42//arrayis0-based
arr[1]=13//assignvaluetoelement
fmt.Printf("Thefirstelementis%d\n",arr[0])
//getelementvalue,itreturns42
fmt.Printf("Thelastelementis%d\n",arr[9])
//itreturnsdefaultvalueof10thelementinthisarray,whichis0inthiscase.
Becauselengthisapartofthearraytype,[3]intand[4]intaredifferenttypes,sowecannotchangethelengthofarrays.Whenyouusearraysasarguments,functionsgettheircopiesinsteadofreferences!Ifyouwanttousereferences,youmaywanttouseslice.We'lltalkaboutlater.
It'spossibletouse:=whenyoudefinearrays.
a:=[3]int{1,2,3}//defineanintarraywith3elements
b:=[10]int{1,2,3}
//defineaintarraywith10elements,ofwhichthefirstthreeareassigned.
//Therestofthemusethedefaultvalue0.
c:=[...]int{4,5,6}//use`…`toreplacethelengthparameterandGowillcalculat
eitforyou.
Youmaywanttousearraysasarrays'elements.Let'sseehowtodothis.
Gofoundation
23
//defineatwo-dimensionalarraywith2elements,andeachelementhas4elements.
doubleArray:=[2][4]int{[4]int{1,2,3,4},[4]int{5,6,7,8}}
//Thedeclarationcanbewrittenmoreconciselyasfollows.
easyArray:=[2][4]int{{1,2,3,4},{5,6,7,8}}
Arrayunderlyingdatastructure.
Figure2.2Multidimensionalarraymappingrelationship
slice
Inmanysituations,thearraytypeisnotagoodchoice-forinstancewhenwedon'tknowhowlongthearraywillbewhenwedefineit.Thus,weneeda"dynamicarray".ThisiscalledsliceinGo.
sliceisnotreallyadynamicarray.It'sareferencetype.slicepointstoanunderlyingarraywhosedeclarationissimilartoarray,butdoesn'tneedlength.
//justlikedefininganarray,butthistime,weexcludethelength.
varfslice[]int
Thenwedefineaslice,andinitializeitsdata.
slice:=[]byte{'a','b','c','d'}
slicecanredefineexistingslicesorarrays.sliceusesarray[i:j]toslice,whereiisthestartindexandjisendindex,butnoticethatarray[j]willnotbeslicedsincethelengthofthesliceisj-i.
//defineanarraywith10elementswhosetypesarebytes
varar=[10]byte{'a','b','c','d','e','f','g','h','i','j'}
//definetwosliceswithtype[]byte
vara,b[]byte
//'a'pointstoelementsfrom3rdto5thinarrayar.
a=ar[2:5]
//now'a'haselementsar[2],ar[3]andar[4]
//'b'isanothersliceofarrayar
b=ar[3:5]
//now'b'haselementsar[3]andar[4]
Gofoundation
24
Noticethedifferencesbetweensliceandarraywhenyoudefinethem.Weuse[…]toletGocalculatelengthbutuse[]todefinesliceonly.
Theirunderlyingdatastructure.
Figure2.3Correspondencebetweensliceandarray
slicehassomeconvenientoperations.
sliceis0-based,ar[:n]equalstoar[0:n]Thesecondindexwillbethelengthofsliceifomitted,ar[n:]equalstoar[n:len(ar)].Youcanusear[:]toslicewholearray,reasonsareexplainedinfirsttwostatements.
Moreexamplespertainingtoslice
//defineanarray
vararray=[10]byte{'a','b','c','d','e','f','g','h','i','j'}
//definetwoslices
varaSlice,bSlice[]byte
//someconvenientoperations
aSlice=array[:3]//equalstoaSlice=array[0:3]aSlicehaselementsa,b,c
aSlice=array[5:]//equalstoaSlice=array[5:10]aSlicehaselementsf,g,h,i,j
aSlice=array[:]//equalstoaSlice=array[0:10]aSlicehasallelements
//slicefromslice
aSlice=array[3:7]//aSlicehaselementsd,e,f,g,len=4,cap=7
bSlice=aSlice[1:3]//bSlicecontainsaSlice[1],aSlice[2],soithaselementse,f
bSlice=aSlice[:3]//bSlicecontainsaSlice[0],aSlice[1],aSlice[2],soithasd,e
,f
bSlice=aSlice[0:5]//slicecouldbeexpandedinrangeofcap,nowbSlicecontainsd
,e,f,g,h
bSlice=aSlice[:]//bSlicehassameelementsasaSlicedoes,whichared,e,f,g
sliceisareferencetype,soanychangeswillaffectothervariablespointingtothesamesliceorarray.Forinstance,inthecaseofaSliceandbSliceabove,ifyouchangethevalueofanelementinaSlice,bSlicewillbechangedaswell.
sliceislikeastructbydefinitionanditcontains3parts.
Apointerthatpointstowhereslicestarts.Thelengthofslice.Capacity,thelengthfromstartindextoendindexofslice.
Gofoundation
25
Array_a:=[10]byte{'a','b','c','d','e','f','g','h','i','j'}
Slice_a:=Array_a[2:5]
Theunderlyingdatastructureofthecodeaboveasfollows.
Figure2.4Arrayinformationofslice
Therearesomebuilt-infunctionsforslice.
lengetsthelengthofslice.capgetsthemaximumlengthofsliceappendappendsoneormoreelementstoslice,andreturnsslice.copycopieselementsfromoneslicetotheother,andreturnsthenumberofelementsthatwerecopied.
Attention:appendwillchangethearraythatslicepointsto,andaffectotherslicesthatpointtothesamearray.Also,ifthereisnotenoughlengthfortheslice((cap-len)==0),appendreturnsanewarrayforthisslice.Whenthishappens,otherslicespointingtotheoldarraywillnotbeaffected.
map
mapbehaveslikeadictionaryinPython.Usetheformmap[keyType]valueTypetodefineit.
Let'sseesomecode.The'set'and'get'valuesinmaparesimilartoslice,howevertheindexinslicecanonlybeoftype'int'whilemapcanusemuchmorethanthat:forexampleint,string,orwhateveryouwant.Also,theyareallabletouse==and!=tocomparevalues.
//usestringasthekeytype,intasthevaluetype,and`make`initializeit.
varnumbersmap[string]int
//anotherwaytodefinemap
numbers:=make(map[string]int)
numbers["one"]=1//assignvaluebykey
numbers["ten"]=10
numbers["three"]=3
fmt.Println("Thethirdnumberis:",numbers["three"])//getvalues
//Itprints:Thethirdnumberis:3
Somenoteswhenyouusemap.
mapisdisorderly.Everytimeyouprintmapyouwillgetdifferentresults.It'simpossible
Gofoundation
26
togetvaluesbyindex-youhavetousekey.mapdoesn'thaveafixedlength.It'sareferencetypejustlikeslice.lenworksformapalso.Itreturnshowmanykeysthatmaphas.It'squiteeasytochangethevaluethroughmap.Simplyusenumbers["one"]=11tochangethevalueofkeyoneto11.
Youcanuseformkey:valtoinitializemap'svalues,andmaphasbuilt-inmethodstocheckifthekeyexists.
Usedeletetodeleteanelementinmap.
//Initializeamap
rating:=map[string]float32{"C":5,"Go":4.5,"Python":4.5,"C++":2}
//maphastworeturnvalues.Forthesecondreturnvalue,ifthekeydoesn't
//exist,'ok'returnsfalse.Itreturnstrueotherwise.
csharpRating,ok:=rating["C#"]
ifok{
fmt.Println("C#isinthemapanditsratingis",csharpRating)
}else{
fmt.Println("WehavenoratingassociatedwithC#inthemap")
}
delete(rating,"C")//deleteelementwithkey"c"
AsIsaidabove,mapisareferencetype.Iftwomapspointtosameunderlyingdata,anychangewillaffectbothofthem.
m:=make(map[string]string)
m["Hello"]="Bonjour"
m1:=m
m1["Hello"]="Salut"//nowthevalueofm["hello"]isSalut
make,new
makedoesmemoryallocationforbuilt-inmodels,suchasmap,slice,andchannel,whilenewisfortypes'memoryallocation.
new(T)allocateszero-valuetotypeT'smemory,returnsitsmemoryaddress,whichisthevalueoftype*T.ByGo'sdefinition,itreturnsapointerwhichpointstotypeT'szero-value.
newreturnspointers.
Thebuilt-infunctionmake(T,args)hasdifferentpurposesthannew(T).makecanbeusedforslice,map,andchannel,andreturnsatypeTwithaninitialvalue.Thereasonfordoingthisisbecausetheunderlyingdataofthesethreetypesmustbeinitializedbefore
Gofoundation
27
theypointtothem.Forexample,aslicecontainsapointerthatpointstotheunderlyingarray,lengthandcapacity.Beforethesedataareinitialized,sliceisnil,soforslice,mapandchannel,makeinitializestheirunderlyingdataandassignssomesuitablevalues.
makereturnsnon-zerovalues.
Thefollowingpictureshowshownewandmakearedifferent.
Figure2.5Underlyingmemoryallocationofmakeandnew
Zero-valuedoesnotmeanemptyvalue.It'sthevaluethatvariablesdefaulttoinmostcases.Hereisalistofsomezero-values.
int0
int80
int320
int640
uint0x0
rune0//theactualtypeofruneisint32
byte0x0//theactualtypeofbyteisuint8
float320//lengthis4byte
float640//lengthis8byte
boolfalse
string""
Gofoundation
28
2.3Controlstatementsandfunctions
Controlstatement
if
ifdoesn'tneedparenthesesinGo.
ifx>10{
//whenxisgreaterthan10
//programentersthisblock
fmt.Println("xisgreaterthan10")
}else{
//whenxissmallerthan10
//programentersthisblock
fmt.Println("xislessthanorequalto10")
}
Goallowsustoinitializeandusevariablesiniflikethis:
//initializex,thencheckifxgreaterthan
ifx:=computedValue();x>10{
fmt.Println("xisgreaterthan10")
}else{
fmt.Println("xislessthan10")
}
//thefollowingcodewillnotcompile
fmt.Println(x)
Formultipleconditionsweusetheelseifblock
ifinteger==3{
fmt.Println("Theintegerisequalto3")
}elseifinteger<3{
fmt.Println("Theintegerislessthan3")
}else{
fmt.Println("Theintegerisgreaterthan3")
}
goto
Controlstatementsandfunctions
29
Gohasagotokeyword,butbecarefulwhenyouuseit.gotoreroutesthecontrolflowtoapreviouslydefinedlabelwithinthebodyofsamecodeblock.
funcmyFunc(){
i:=0
Here://labelendswith":"
fmt.Println(i)
i++
gotoHere//jumptolabel"Here"
}
Thelabelnameiscasesensitive.
for
Godoesnothavewhile,dowhile.Justafor,whichisthemostpowerfulcontrollogic.Itcanreaddatainloopsanditerativeoperations,justlikewhile.Likeif,fordoesn'tneedparenthesis.
forexpression1;expression2;expression3{
//...
}
packagemain
import"fmt"
funcmain(){
sum:=0;
forindex:=0;index<10;index++{
sum+=index
}
fmt.Println("sumisequalto",sum)
}
//Print:sumisequalto45
Wecanomitoneormoreexpressions.
sum:=1
for;sum<1000;{
sum+=sum
}
for{
//thisisaninfiniteloop
}
Controlstatementsandfunctions
30
Usingforlikeawhile
sum:=1
forsum<1000{
sum+=sum
}
breakandcontinue
1. break:jumpsoutoftheloop.Ifyouhavenestedloops,usebreakalongwithlabels.2. continueskipsthecurrentloopandstartsthenextone
forindex:=10;index>0;index--{
ifindex==5{
break//orcontinue
}
fmt.Println(index)
}//breakprints10、9、8、7、6//continueprints10、9、8、7、6、4、3、2、1
forcanreaddatafromsliceandmapwhenitisusedtogetherwithrange.
fork,v:=rangemap{
fmt.Println("map'skey:",k)
fmt.Println("map'sval:",v)
}
BecauseGosupportsmulti-valuereturnsandgivescompileerrorswhenyoudon'tusevaluesthatweredefined,youmaywanttouse_todiscardcertainreturnvalues.
for_,v:=rangemap{
fmt.Println("map'sval:",v)
}
switch
Sometimesyoumayfindthatyouareusingtoomanyif-elsestatementstoimplementsomelogic,whichmaymakeitdifficulttoreadandmaintaininthefuture.Theswitchstatementsolvesthisproblem.
Controlstatementsandfunctions
31
switchsExpr{
caseexpr1:
someinstructions
caseexpr2:
someotherinstructions
caseexpr3:
someotherinstructions
default:
othercode
}
ThetypeofsExpr,expr1,expr2,andexpr3mustbethesame.switchisveryflexible.Conditionsdon'thavetobeconstantsanditexecutesfromtoptobottomuntilitmatchesconditions.Ifthereisnostatementafterthekeywordswitch,thenitmatchestrue.
i:=10
switchi{
case1:
fmt.Println("iisequalto1")
case2,3,4:
fmt.Println("iisequalto2,3or4")
case10:
fmt.Println("iisequalto10")
default:
fmt.Println("AllIknowisthatiisaninteger")
}
Inthefifthline,weputmanyvaluesinonecase,andwedon'tneedtoaddthebreakkeywordattheendofcase'sbody.Itwilljumpoutoftheswitchbodyonceitmatchedanycase.Ifyouwanttocontinuetomatchingmorecases,youneedtousethefallthroughstatement.
Controlstatementsandfunctions
32
integer:=6
switchinteger{
case4:
fmt.Println("integer<=4")
fallthrough
case5:
fmt.Println("integer<=5")
fallthrough
case6:
fmt.Println("integer<=6")
fallthrough
case7:
fmt.Println("integer<=7")
fallthrough
case8:
fmt.Println("integer<=8")
fallthrough
default:
fmt.Println("defaultcase")
}
Thisprogramprintsthefollowinginformation.
integer<=6
integer<=7
integer<=8
defaultcase
FunctionsUsethefunckeywordtodefineafunction.
funcfuncName(input1type1,input2type2)(output1type1,output2type2){
//functionbody
//multi-valuereturn
returnvalue1,value2
}
Wecanextrapolatethefollowinginformationfromtheexampleabove.
UsekeywordfunctodefineafunctionfuncName.Functionshavezero,oneormorethanonearguments.Theargumenttypecomesaftertheargumentnameandargumentsareseparatedby,.Functionscanreturnmultiplevalues.Therearetworeturnvaluesnamedoutput1andoutput2,youcanomittheirnames
Controlstatementsandfunctions
33
andusetheirtypeonly.Ifthereisonlyonereturnvalueandyouomittedthename,youdon'tneedbracketsforthereturnvalues.Ifthefunctiondoesn'thavereturnvalues,youcanomitthereturnparametersaltogether.Ifthefunctionhasreturnvalues,youhavetousethereturnstatementsomewhereinthebodyofthefunction.
Let'sseeonepracticalexample.(calculatemaximumvalue)
packagemain
import"fmt"
//returngreatervaluebetweenaandb
funcmax(a,bint)int{
ifa>b{
returna
}
returnb
}
funcmain(){
x:=3
y:=4
z:=5
max_xy:=max(x,y)//callfunctionmax(x,y)
max_xz:=max(x,z)//callfunctionmax(x,z)
fmt.Printf("max(%d,%d)=%d\n",x,y,max_xy)
fmt.Printf("max(%d,%d)=%d\n",x,z,max_xz)
fmt.Printf("max(%d,%d)=%d\n",y,z,max(y,z))//callfunctionhere
}
Intheaboveexample,therearetwoargumentsinthefunctionmax,theirtypesarebothintsothefirsttypecanbeomitted.Forinstance,a,bintinsteadofaint,bint.Thesamerulesapplyforadditionalarguments.Noticeherethatmaxonlyhasonereturnvalue,soweonlyneedtowritethetypeofitsreturnvalue-thisistheshortformofwritingit.
Multi-valuereturn
Controlstatementsandfunctions
34
packagemain
import"fmt"
//returnresultsofA+BandA*B
funcSumAndProduct(A,Bint)(int,int){
returnA+B,A*B
}
funcmain(){
x:=3
y:=4
xPLUSy,xTIMESy:=SumAndProduct(x,y)
fmt.Printf("%d+%d=%d\n",x,y,xPLUSy)
fmt.Printf("%d*%d=%d\n",x,y,xTIMESy)
}
Theaboveexamplereturnstwovalueswithoutnames-youhavetheoptionofnamingthemalso.Ifwenamedthereturnvalues,wewouldjustneedtousereturntoreturnthevaluessincetheyareinitializedinthefunctionautomatically.Noticethatifyourfunctionsaregoingtobeusedoutsideofthepackage,whichmeansyourfunctionnamesstartwithacapitalletter,you'dbetterwritecompletestatementsforreturn;itmakesyourcodemorereadable.
funcSumAndProduct(A,Bint)(addint,multipliedint){
add=A+B
multiplied=A*B
return
}
Variadicfunctions
Gosupportsfunctionswithavariablenumberofarguments.Thesefunctionsarecalled"variadic",whichmeansthefunctionallowsanuncertainnumbersofarguments.
funcmyfunc(arg...int){}
arg…inttellsGothatthisisafunctionthathasvariablearguments.Noticethattheseargumentsaretypeint.Inthebodyoffunction,theargbecomesasliceofint.
for_,n:=rangearg{
fmt.Printf("Andthenumberis:%d\n",n)
}
Controlstatementsandfunctions
35
Passbyvalueandpointers
Whenwepassanargumenttothefunctionthatwascalled,thatfunctionactuallygetsthecopyofourvariablessoanychangewillnotaffecttotheoriginalvariable.
Let'sseeoneexampleinordertoprovewhati'msaying.
packagemain
import"fmt"
//simplefunctiontoadd1toa
funcadd1(aint)int{
a=a+1//wechangevalueofa
returna//returnnewvalueofa
}
funcmain(){
x:=3
fmt.Println("x=",x)//shouldprint"x=3"
x1:=add1(x)//calladd1(x)
fmt.Println("x+1=",x1)//shouldprint"x+1=4"
fmt.Println("x=",x)//shouldprint"x=3"
}
Eventhoughwecalledadd1withx,theoriginvalueofxdoesn'tchange.
Thereasonisverysimple:whenwecalledadd1,wegaveacopyofxtoit,notthexitself.
NowyoumayaskhowIcanpasstherealxtothefunction.
Weneedusepointershere.Weknowvariablesarestoredinmemoryandtheyhavesomememoryaddresses.So,ifwewanttochangethevalueofavariable,wemustchangeitsmemoryaddress.Thereforethefunctionadd1hastoknowthememoryaddressofxinordertochangeitsvalue.Herewepass&xtothefunction,andchangetheargument'stypetothepointertype*int.Beawarethatwepassacopyofthepointer,notcopyofvalue.
Controlstatementsandfunctions
36
packagemain
import"fmt"
//simplefunctiontoadd1toa
funcadd1(a*int)int{
*a=*a+1//wechangedvalueofa
return*a//returnnewvalueofa
}
funcmain(){
x:=3
fmt.Println("x=",x)//shouldprint"x=3"
x1:=add1(&x)//calladd1(&x)passmemoryaddressofx
fmt.Println("x+1=",x1)//shouldprint"x+1=4"
fmt.Println("x=",x)//shouldprint"x=4"
}
Nowwecanchangethevalueofxinthefunctions.Whydoweusepointers?Whataretheadvantages?
Allowsustousemorefunctionstooperateononevariable.Lowcostbypassingmemoryaddresses(8bytes),copyisnotanefficientway,bothintermsoftimeandspace,topassvariables.string,sliceandmaparereferencetypes,sotheyusepointerswhenpassingtofunctionsbydefault.(Attention:Ifyouneedtochangethelengthofslice,youhavetopasspointersexplicitly)
defer
Gohasawelldesignedkeywordcalleddefer.Youcanhavemanydeferstatementsinonefunction;theywillexecuteinreverseorderwhentheprogramexecutestotheendoffunctions.Inthecasewheretheprogramopenssomeresourcefiles,thesefileswouldhavetobeclosedbeforethefunctioncanreturnwitherrors.Let'sseesomeexamples.
Controlstatementsandfunctions
37
funcReadWrite()bool{
file.Open("file")
//Dosomework
iffailureX{
file.Close()
returnfalse
}
iffailureY{
file.Close()
returnfalse
}
file.Close()
returntrue
}
Wesawsomecodebeingrepeatedseveraltimes.defersolvesthisproblemverywell.Itdoesn'tonlyhelpyoutowritecleancodebutalsomakesyourcodemorereadable.
funcReadWrite()bool{
file.Open("file")
deferfile.Close()
iffailureX{
returnfalse
}
iffailureY{
returnfalse
}
returntrue
}
Iftherearemorethanonedefers,theywillexecutebyreverseorder.Thefollowingexamplewillprint43210.
fori:=0;i<5;i++{
deferfmt.Printf("%d",i)
}
Functionsasvaluesandtypes
FunctionsarealsovariablesinGo,wecanusetypetodefinethem.Functionsthathavethesamesignaturecanbeseenasthesametype.
Controlstatementsandfunctions
38
typetypeNamefunc(input1inputType1,input2inputType2[,...])(result1resultType1
[,...])
What'stheadvantageofthisfeature?Theansweristhatitallowsustopassfunctionsasvalues.
packagemain
import"fmt"
typetestIntfunc(int)bool//defineafunctiontypeofvariable
funcisOdd(integerint)bool{
ifinteger%2==0{
returnfalse
}
returntrue
}
funcisEven(integerint)bool{
ifinteger%2==0{
returntrue
}
returnfalse
}
//passthefunction`f`asanargumenttoanotherfunction
funcfilter(slice[]int,ftestInt)[]int{
varresult[]int
for_,value:=rangeslice{
iff(value){
result=append(result,value)
}
}
returnresult
}
funcmain(){
slice:=[]int{1,2,3,4,5,7}
fmt.Println("slice=",slice)
odd:=filter(slice,isOdd)//usefunctionasvalues
fmt.Println("Oddelementsofsliceare:",odd)
even:=filter(slice,isEven)
fmt.Println("Evenelementsofsliceare:",even)
}
It'sveryusefulwhenweuseinterfaces.AsyoucanseetestIntisavariablethathasafunctionastypeandthereturnedvaluesandargumentsoffilterarethesameasthoseoftestInt.Therefore,wecanhavecomplexlogicinourprograms,whilemaintaining
Controlstatementsandfunctions
39
flexibilityinourcode.
PanicandRecover
Godoesn'thavetry-catchstructurelikeJavadoes.Insteadofthrowingexceptions,Gousespanicandrecovertodealwitherrors.However,youshouldn'tusepanicverymuch,althoughit'spowerful.
Panicisabuilt-infunctiontobreakthenormalflowofprogramsandgetintopanicstatus.WhenafunctionFcallspanic,Fwillnotcontinueexecutingbutitsdeferfunctionswillcontinuetoexecute.ThenFgoesbacktothebreakpointwhichcausedthepanicstatus.Theprogramwillnotterminateuntilallofthesefunctionsreturnwithpanictothefirstlevelofthatgoroutine.paniccanbeproducedbycallingpanicintheprogram,andsomeerrorsalsocausepaniclikearrayaccessoutofboundserrors.
Recoverisabuilt-infunctiontorecovergoroutinesfrompanicstatus.Callingrecoverindeferfunctionsisusefulbecausenormalfunctionswillnotbeexecutedwhentheprogramisinthepanicstatus.Itcatchespanicvaluesiftheprogramisinthepanicstatus,anditgetsniliftheprogramisnotinpanicstatus.
Thefollowingexampleshowshowtousepanic.
varuser=os.Getenv("USER")
funcinit(){
ifuser==""{
panic("novaluefor$USER")
}
}
Thefollowingexampleshowshowtocheckpanic.
functhrowsPanic(ffunc())(bbool){
deferfunc(){
ifx:=recover();x!=nil{
b=true
}
}()
f()//iffcausespanic,itwillrecover
return
}
mainfunctionandinitfunction
Controlstatementsandfunctions
40
Gohastworetentionswhicharecalledmainandinit,whereinitcanbeusedinallpackagesandmaincanonlybeusedinthemainpackage.Thesetwofunctionsarenotabletohaveargumentsorreturnvalues.Eventhoughwecanwritemanyinitfunctionsinonepackage,Istronglyrecommendwritingonlyoneinitfunctionforeachpackage.
Goprogramswillcallinit()andmain()automatically,soyoudon'tneedtocallthembyyourself.Foreverypackage,theinitfunctionisoptional,butpackagemainhasoneandonlyonemainfunction.
Programsinitializeandbeginexecutionfromthemainpackage.Ifthemainpackageimportsotherpackages,theywillbeimportedinthecompiletime.Ifonepackageisimportedmanytimes,itwillbeonlycompiledonce.Afterimportingpackages,programswillinitializetheconstantsandvariableswithintheimportedpackages,thenexecutetheinitfunctionifitexists,andsoon.Afteralltheotherpackagesareinitialized,programswillinitializeconstantsandvariablesinthemainpackage,thenexecutetheinitfunctioninsidethepackageifitexists.Thefollowingfigureshowstheprocess.
Figure2.6FlowofprogramsinitializationinGo
import
WeuseimportveryofteninGoprogramsasfollows.
import(
"fmt"
)
Thenweusefunctionsinthatpackageasfollows.
fmt.Println("helloworld")
fmtisfromGostandardlibrary,itislocatedwithin$GOROOT/pkg.Gosupportsthird-partypackagesintwoways.
1. Relativepathimport"./model"//loadpackageinthesamedirectory,Idon'trecommendthisway.
2. Absolutepathimport"shorturl/model"//loadpackageinpath"$GOPATH/pkg/shorturl/model"
Therearesomespecialoperatorswhenweimportpackages,andbeginnersarealwaysconfusedbytheseoperators.
Controlstatementsandfunctions
41
1. Dotoperator.Sometimeweseepeopleusefollowingwaytoimportpackages.
import(
."fmt"
)
Thedotoperatormeansyoucanomitthepackagenamewhenyoucallfunctionsinsideofthatpackage.Nowfmt.Printf("Helloworld")becomestoPrintf("Helloworld").
2. Aliasoperation.Itchangesthenameofthepackagethatweimportedwhenwecallfunctionsthatbelongtothatpackage.
import(
f"fmt"
)
Nowfmt.Printf("Helloworld")becomestof.Printf("Helloworld").
3. _operator.Thisistheoperatorthatisdifficulttounderstandwithoutsomeoneexplainingittoyou.
import(
"database/sql"
_"github.com/ziutek/mymysql/godrv"
)
The_operatoractuallymeanswejustwanttoimportthatpackageandexecuteitsinitfunction,andwearenotsureifwanttousethefunctionsbelongingtothatpackage.
Controlstatementsandfunctions
42
2.4struct
structWecandefinenewtypesofcontainersofotherpropertiesorfieldsinGojustlikeinotherprogramminglanguages.Forexample,wecancreateatypecalledpersontorepresentaperson,withfieldsnameandage.Wecallthiskindoftypeastruct.
typepersonstruct{
namestring
ageint
}
Therearetwofields.
nameisastringusedtostoreaperson'sname.ageisaintusedtostoreaperson'sage.
Let'sseehowtouseit.
typepersonstruct{
namestring
ageint
}
varPperson//pispersontype
P.name="Astaxie"//assign"Astaxie"tothefield'name'ofp
P.age=25//assign25tofield'age'ofp
fmt.Printf("Theperson'snameis%s\n",P.name)//accessfield'name'ofp
Therearethreemorewaystodefineastruct.
Assigninitialvaluesbyorder
P:=person{"Tom",25}
Usetheformatfield:valuetoinitializethestructwithoutorder
P:=person{age:24,name:"Bob"}
struct
43
Defineananonymousstruct,theninitializeit
P:=struct{namestring;ageint}{"Amy",18}
Let'sseeacompleteexample.
packagemain
import"fmt"
//defineanewtype
typepersonstruct{
namestring
ageint
}
//comparetheageoftwopeople,thenreturnthe
//olderpersonanddifferencesofage
//structispassedbyvalue
funcOlder(p1,p2person)(person,int){
ifp1.age>p2.age{
returnp1,p1.age-p2.age
}
returnp2,p2.age-p1.age
}
funcmain(){
vartomperson
//initialization
tom.name,tom.age="Tom",18
//initializetwovaluesbyformat"field:value"
bob:=person{age:25,name:"Bob"}
//initializetwovalueswithorder
paul:=person{"Paul",43}
tb_Older,tb_diff:=Older(tom,bob)
tp_Older,tp_diff:=Older(tom,paul)
bp_Older,bp_diff:=Older(bob,paul)
fmt.Printf("Of%sand%s,%sisolderby%dyears\n",
tom.name,bob.name,tb_Older.name,tb_diff)
fmt.Printf("Of%sand%s,%sisolderby%dyears\n",
tom.name,paul.name,tp_Older.name,tp_diff)
fmt.Printf("Of%sand%s,%sisolderby%dyears\n",
bob.name,paul.name,bp_Older.name,bp_diff)
}
struct
44
embeddedfieldsinstruct
I'vejustintroducedtoyouhowtodefineastructwithfieldnamesandtype.Infact,Gosupportsfieldswithoutnames,butwithtypes.Wecalltheseembeddedfields.
Whentheembeddedfieldisastruct,allthefieldsinthatstructwillimplicitlybethefieldsinthestructinwhichithasbeenembdedded.
Let'sseeoneexample.
packagemain
import"fmt"
typeHumanstruct{
namestring
ageint
weightint
}
typeStudentstruct{
Human//embeddedfield,itmeansStudentstruct
//includesallfieldsthatHumanhas.
specialtystring
}
funcmain(){
//initializeastudent
mark:=Student{Human{"Mark",25,120},"ComputerScience"}
//accessfields
fmt.Println("Hisnameis",mark.name)
fmt.Println("Hisageis",mark.age)
fmt.Println("Hisweightis",mark.weight)
fmt.Println("Hisspecialtyis",mark.specialty)
//modifynotes
mark.specialty="AI"
fmt.Println("Markchangedhisspecialty")
fmt.Println("Hisspecialtyis",mark.specialty)
//modifyage
fmt.Println("Markbecomeold")
mark.age=46
fmt.Println("Hisageis",mark.age)
//modifyweight
fmt.Println("Markisnotanathletanymore")
mark.weight+=60
fmt.Println("Hisweightis",mark.weight)
}
struct
45
Figure2.7EmbeddinginStudentandHuman
WeseethatwecanaccesstheageandnamefieldsinStudentjustlikewecaninHuman.Thisishowembeddedfieldswork.It'sverycool,isn'tit?Holdon,there'ssomethingcooler!YoucanevenuseStudenttoaccessHumaninthisembeddedfield!
mark.Human=Human{"Marcus",55,220}
mark.Human.age-=1
AllthetypesinGocanbeusedasembeddedfields.
packagemain
import"fmt"
typeSkills[]string
typeHumanstruct{
namestring
ageint
weightint
}
typeStudentstruct{
Human//structasembeddedfield
Skills//stringsliceasembeddedfield
int//built-intypeasembeddedfield
specialtystring
}
funcmain(){
//initializeStudentJane
jane:=Student{Human:Human{"Jane",35,100},specialty:"Biology"}
//accessfields
fmt.Println("Hernameis",jane.name)
fmt.Println("Herageis",jane.age)
fmt.Println("Herweightis",jane.weight)
fmt.Println("Herspecialtyis",jane.specialty)
//modifyvalueofskillfield
jane.Skills=[]string{"anatomy"}
fmt.Println("Herskillsare",jane.Skills)
fmt.Println("Sheacquiredtwonewones")
jane.Skills=append(jane.Skills,"physics","golang")
fmt.Println("Herskillsnoware",jane.Skills)
//modifyembeddedfield
jane.int=3
fmt.Println("Herpreferrednumberis",jane.int)
}
struct
46
Intheaboveexample,wecanseethatalltypescanbeembeddedfieldsandwecanusefunctionstooperateonthem.
Thereisonemoreproblemhowever.IfHumanhasafieldcalledphoneandStudenthasafieldwithsamename,whatshouldwedo?
Gouseaverysimplewaytosolveit.Theouterfieldsgetupperaccesslevels,whichmeanswhenyouaccessstudent.phone,wewillgetthefieldcalledphoneinstudent,nottheoneintheHumanstruct.Thisfeaturecanbesimplyseenasfieldoverloading.
packagemain
import"fmt"
typeHumanstruct{
namestring
ageint
phonestring//Humanhasphonefield
}
typeEmployeestruct{
Human//embeddedfieldHuman
specialtystring
phonestring//phoneinemployee
}
funcmain(){
Bob:=Employee{Human{"Bob",34,"777-444-XXXX"},
"Designer","333-222"}
fmt.Println("Bob'sworkphoneis:",Bob.phone)
//accessphonefieldinHuman
fmt.Println("Bob'spersonalphoneis:",Bob.Human.phone)
}
struct
47
Object-orientedGodoesn'tallowustohavefunctionsasapartofstructs,butitdoesallowustobindfunctionstostructs,thesefunctionsarecalledmethods.Thesemethodscanonlybecalledbyaninstanceofthestruct.
methodSupposeyoudefinea"rectangle"structandyouwanttocalculateitsarea.We'dtypicallyusethefollowingcodetoachievethisgoal.
packagemain
import"fmt"
typeRectanglestruct{
width,heightfloat64
}
funcarea(rRectangle)float64{
returnr.width*r.height
}
funcmain(){
r1:=Rectangle{12,2}
r2:=Rectangle{9,4}
fmt.Println("Areaofr1is:",area(r1))
fmt.Println("Areaofr2is:",area(r2))
}
Theaboveexamplecancalculatearectangle'sarea.Weusethefunctioncalledarea,butit'snotamethodoftherectanglestruct(likeclassmethodsinclassicobject-orientedlanguages).Thefunctionandstructaretwoindependentthingsasyoumaynotice.
It'snotaproblemsofar.However,ifyoualsohavetocalculatetheareaofacircle,square,pentagon,oranyotherkindofshape,youaregoingtoneedtoaddadditionalfunctionswithverysimilarnames.
Figure2.8Relationshipbetweenfunctionandstruct
Obviouslythat'snotcool.Also,theareashouldreallybethepropertyofacircleorrectangle.
Object-oriented
48
Forthosereasons,wehavethemethodconcept.methodisaffiliatedwithtype.Ithasthesamesyntaxasfunctionsdoexceptforanadditionalparameterafterthefunckeywordcalledthereceiver,whichisthemainbodyofthatmethod.
Usingthesameexample,Rectangle.area()belongsdirectlytorectangle,insteadofasaperipheralfunction.Morespecifically,length,widthandarea()allbelongtorectangle.
AsRobPikesaid.
"Amethodisafunctionwithanimplicitfirstargument,calledareceiver."
Syntaxofmethod.
func(rReceiverType)funcName(parameters)(results)
Let'schangeourexampleusingmethodinstead.
Object-oriented
49
packagemain
import(
"fmt"
"math"
)
typeRectanglestruct{
width,heightfloat64
}
typeCirclestruct{
radiusfloat64
}
func(rRectangle)area()float64{
returnr.width*r.height
}
func(cCircle)area()float64{
returnc.radius*c.radius*math.Pi
}
funcmain(){
r1:=Rectangle{12,2}
r2:=Rectangle{9,4}
c1:=Circle{10}
c2:=Circle{25}
fmt.Println("Areaofr1is:",r1.area())
fmt.Println("Areaofr2is:",r2.area())
fmt.Println("Areaofc1is:",c1.area())
fmt.Println("Areaofc2is:",c2.area())
}
Notesforusingmethods.
Ifthenameofmethodsarethesamebuttheydon'tsharethesamereceivers,theyarenotthesame.Methodsareabletoaccessfieldswithinreceivers.Use.tocallamethodinthestruct,thesamewayfieldsarecalled.
Figure2.9Methodsaredifferentindifferentstructs
Intheexampleabove,thearea()methodsbelongtobothRectangleandCirclerespectively,sothereceiversareRectangleandCircle.
Object-oriented
50
Onethingthat'sworthnotingisthatthemethodwithadottedlinemeansthereceiverispassedbyvalue,notbyreference.Thedifferencebetweenthemisthatamethodcanchangeitsreceiver'svalueswhenthereceiverispassedbyreference,anditgetsacopyofthereceiverwhenthereceiverispassedbyvalue.
Canthereceiveronlybeastruct?Ofcoursenot.Anytypecanbethereceiverofamethod.Youmaybeconfusedaboutcustomizedtypes.Structisaspecialkindofcustomizedtype-therearemorecustomizedtypes.
Usethefollowingformattodefineacustomizedtype.
typetypeNametypeLiteral
Examplesofcustomizedtypes:
typeagesint
typemoneyfloat32
typemonthsmap[string]int
m:=months{
"January":31,
"February":28,
...
"December":31,
}
Ihopethatyouknowhowtousecustomizedtypesnow.SimilartotypedefinC,weuseagestosubstituteintintheaboveexample.
Let'sgetbacktotalkingaboutmethod.
Youcanuseasmanymethodsincustomtypesasyouwant.
packagemain
import"fmt"
const(
WHITE=iota
BLACK
BLUE
RED
YELLOW
)
typeColorbyte
Object-oriented
51
typeBoxstruct{
width,height,depthfloat64
colorColor
}
typeBoxList[]Box//asliceofboxes
func(bBox)Volume()float64{
returnb.width*b.height*b.depth
}
func(b*Box)SetColor(cColor){
b.color=c
}
func(blBoxList)BiggestsColor()Color{
v:=0.00
k:=Color(WHITE)
for_,b:=rangebl{
ifb.Volume()>v{
v=b.Volume()
k=b.color
}
}
returnk
}
func(blBoxList)PaintItBlack(){
fori,_:=rangebl{
bl[i].SetColor(BLACK)
}
}
func(cColor)String()string{
strings:=[]string{"WHITE","BLACK","BLUE","RED","YELLOW"}
returnstrings[c]
}
funcmain(){
boxes:=BoxList{
Box{4,4,4,RED},
Box{10,10,1,YELLOW},
Box{1,1,20,BLACK},
Box{10,10,1,BLUE},
Box{10,30,1,WHITE},
Box{20,20,20,YELLOW},
}
fmt.Printf("Wehave%dboxesinourset\n",len(boxes))
fmt.Println("Thevolumeofthefirstoneis",boxes[0].Volume(),"cm³")
fmt.Println("Thecolorofthelastoneis",boxes[len(boxes)-1].color.String())
fmt.Println("Thebiggestoneis",boxes.BiggestsColor().String())
Object-oriented
52
fmt.Println("Let'spaintthemallblack")
boxes.PaintItBlack()
fmt.Println("Thecolorofthesecondoneis",boxes[1].color.String())
fmt.Println("Obviously,now,thebiggestoneis",boxes.BiggestsColor().String())
}
Wedefinesomeconstantsandcustomizedtypes.
UseColorasaliasofbyte.DefineastructBoxwhichhasfieldsheight,width,lengthandcolor.DefineastructBoxListwhichhasBoxasitsfield.
Thenwedefinedsomemethodsforourcustomizedtypes.
Volume()usesBoxasitsreceiverandreturnsthevolumeofBox.SetColor(cColor)changesBox'scolor.BiggestsColor()returnsthecolorwhichhasthebiggestvolume.PaintItBlack()setscolorforallBoxinBoxListtoblack.String()useColorasitsreceiver,returnsthestringformatofcolorname.
Isitmuchclearerwhenweusewordstodescribeourrequirements?Weoftenwriteourrequirementsbeforewestartcoding.
Usepointerasreceiver
Let'stakealookatSetColormethod.ItsreceiverisapointerofBox.Yes,youcanuse*Boxasareceiver.Whydoweuseapointerhere?BecausewewanttochangeBox'scolorinthismethod.Thus,ifwedon'tuseapointer,itwillonlychangethevalueinsideacopyofBox.
Ifweseethatareceiveristhefirstargumentofamethod,it'snothardtounderstandhowitworks.
Youmightbeaskingwhywearen'tusing(*b).Color=cinsteadofb.Color=cintheSetColor()method.EitheroneisOKherebecauseGoknowshowtointerprettheassignment.DoyouthinkGoismorefascinatingnow?
Youmayalsobeaskingwhetherweshoulduse(&bl[i]).SetColor(BLACK)inPaintItBlackbecausewepassapointertoSetColor.Again,eitheroneisOKbecauseGoknowshowtointerpretit!
Inheritanceofmethod
Object-oriented
53
Welearnedaboutinheritanceoffieldsinthelastsection.Similarly,wealsohavemethodinheritanceinGo.Ifananonymousfieldhasmethods,thenthestructthatcontainsthefieldwillhaveallthemethodsfromitaswell.
packagemain
import"fmt"
typeHumanstruct{
namestring
ageint
phonestring
}
typeStudentstruct{
Human//anonymousfield
schoolstring
}
typeEmployeestruct{
Human
companystring
}
//defineamethodinHuman
func(h*Human)SayHi(){
fmt.Printf("Hi,Iam%syoucancallmeon%s\n",h.name,h.phone)
}
funcmain(){
mark:=Student{Human{"Mark",25,"222-222-YYYY"},"MIT"}
sam:=Employee{Human{"Sam",45,"111-888-XXXX"},"GolangInc"}
mark.SayHi()
sam.SayHi()
}
Methodoverload
IfwewantEmployeetohaveitsownmethodSayHi,wecandefineamethodthathasthesamenameinEmployee,anditwillhideSayHiinHumanwhenwecallit.
Object-oriented
54
packagemain
import"fmt"
typeHumanstruct{
namestring
ageint
phonestring
}
typeStudentstruct{
Human
schoolstring
}
typeEmployeestruct{
Human
companystring
}
func(h*Human)SayHi(){
fmt.Printf("Hi,Iam%syoucancallmeon%s\n",h.name,h.phone)
}
func(e*Employee)SayHi(){
fmt.Printf("Hi,Iam%s,Iworkat%s.Callmeon%s\n",e.name,
e.company,e.phone)//Yesyoucansplitinto2lineshere.
}
funcmain(){
mark:=Student{Human{"Mark",25,"222-222-YYYY"},"MIT"}
sam:=Employee{Human{"Sam",45,"111-888-XXXX"},"GolangInc"}
mark.SayHi()
sam.SayHi()
}
YouareabletowriteanObject-orientedprogramnow,andmethodsuseruleofcapitallettertodecidewhetherpublicorprivateaswell.
Object-oriented
55
2.6Interface
InterfaceOneofthesubtlestdesignfeaturesinGoareinterfaces.Afterreadingthissection,youwilllikelybeimpressedbytheirimplementation.
Whatisaninterface
Inshort,aninterfaceisasetofmethodsthatweusetodefineasetofactions.
Liketheexamplesinprevioussections,bothStudentandEmployeecanSayHi(),buttheydon'tdothesamething.
Let'sdosomemorework.We'lladdonemoremethodSing()tothem,alongwiththeBorrowMoney()methodtoStudentandtheSpendSalary()methodtoEmployee.
Now,StudenthasthreemethodscalledSayHi(),Sing()andBorrowMoney(),andEmployeehasSayHi(),Sing()andSpendSalary().
ThiscombinationofmethodsiscalledaninterfaceandisimplementedbybothStudentandEmployee.So,StudentandEmployeeimplementtheinterface:SayHi()andSing().Atthesametime,Employeedoesn'timplementtheinterface:SayHi(),Sing(),BorrowMoney(),andStudentdoesn'timplementtheinterface:SayHi(),Sing(),SpendSalary().ThisisbecauseEmployeedoesn'thavethemethodBorrowMoney()andStudentdoesn'thavethemethodSpendSalary().
TypeofInterface
Aninterfacedefinesasetofmethods,soifatypeimplementsallthemethodswesaythatitimplementstheinterface.
typeHumanstruct{
namestring
ageint
phonestring
}
typeStudentstruct{
Human
schoolstring
loanfloat32
interface
56
}
typeEmployeestruct{
Human
companystring
moneyfloat32
}
func(h*Human)SayHi(){
fmt.Printf("Hi,Iam%syoucancallmeon%s\n",h.name,h.phone)
}
func(h*Human)Sing(lyricsstring){
fmt.Println("Lala,lalala,lalalalala...",lyrics)
}
func(h*Human)Guzzle(beerSteinstring){
fmt.Println("GuzzleGuzzleGuzzle...",beerStein)
}
//EmployeeoverloadsSayhi
func(e*Employee)SayHi(){
fmt.Printf("Hi,Iam%s,Iworkat%s.Callmeon%s\n",e.name,
e.company,e.phone)//Yesyoucansplitinto2lineshere.
}
func(s*Student)BorrowMoney(amountfloat32){
s.loan+=amount//(againandagainand...)
}
func(e*Employee)SpendSalary(amountfloat32){
e.money-=amount//Morevodkaplease!!!Getmethroughtheday!
}
//defineinterface
typeMeninterface{
SayHi()
Sing(lyricsstring)
Guzzle(beerSteinstring)
}
typeYoungChapinterface{
SayHi()
Sing(songstring)
BorrowMoney(amountfloat32)
}
typeElderlyGentinterface{
SayHi()
Sing(songstring)
SpendSalary(amountfloat32)
}
interface
57
Weknowthataninterfacecanbeimplementedbyanytype,andonetypecanimplementmanyinterfacessimultaneously.
Notethatanytypeimplementstheemptyinterfaceinterface{}becauseitdoesn'thaveanymethodsandalltypeshavezeromethodsbydefault.
Valueofinterface
Sowhatkindofvaluescanbeputintheinterface?Ifwedefineavariableasatypeinterface,anytypethatimplementstheinterfacecanassignedtothisvariable.
Liketheaboveexample,ifwedefineavariable"m"asinterfaceMen,thenanyoneofStudent,HumanorEmployeecanbeassignedto"m".SowecouldhaveasliceofMen,andanytypethatimplementsinterfaceMencanassigntothisslice.Beawarehoweverthatthesliceofinterfacedoesn'thavethesamebehaviorasasliceofothertypes.
packagemain
import"fmt"
typeHumanstruct{
namestring
ageint
phonestring
}
typeStudentstruct{
Human
schoolstring
loanfloat32
}
typeEmployeestruct{
Human
companystring
moneyfloat32
}
func(hHuman)SayHi(){
fmt.Printf("Hi,Iam%syoucancallmeon%s\n",h.name,h.phone)
}
func(hHuman)Sing(lyricsstring){
fmt.Println("Lalalala...",lyrics)
}
func(eEmployee)SayHi(){
fmt.Printf("Hi,Iam%s,Iworkat%s.Callmeon%s\n",e.name,
e.company,e.phone)//Yesyoucansplitinto2lineshere.
interface
58
}
//InterfaceMenimplementedbyHuman,StudentandEmployee
typeMeninterface{
SayHi()
Sing(lyricsstring)
}
funcmain(){
mike:=Student{Human{"Mike",25,"222-222-XXX"},"MIT",0.00}
paul:=Student{Human{"Paul",26,"111-222-XXX"},"Harvard",100}
sam:=Employee{Human{"Sam",36,"444-222-XXX"},"GolangInc.",1000}
tom:=Employee{Human{"Sam",36,"444-222-XXX"},"ThingsLtd.",5000}
//defineinterfacei
variMen
//icanstoreStudent
i=mike
fmt.Println("ThisisMike,aStudent:")
i.SayHi()
i.Sing("Novemberrain")
//icanstoreEmployee
i=tom
fmt.Println("ThisisTom,anEmployee:")
i.SayHi()
i.Sing("Borntobewild")
//sliceofMen
fmt.Println("Let'suseasliceofMenandseewhathappens")
x:=make([]Men,3)
//thesethreeelementsaredifferenttypesbuttheyallimplementedinterfaceMen
x[0],x[1],x[2]=paul,sam,mike
for_,value:=rangex{
value.SayHi()
}
}
Aninterfaceisasetofabstractmethods,andcanbeimplementedbynon-interfacetypes.Itcannotthereforeimplementitself.
Emptyinterface
Anemptyinterfaceisaninterfacethatdoesn'tcontainanymethods,soalltypesimplementanemptyinterface.Thisfactisveryusefulwhenwewanttostorealltypesatsomepoint,andissimilartovoid*inC.
interface
59
//defineaasemptyinterface
varainterface{}
variint=5
s:="Helloworld"
//acanstorevalueofanytype
a=i
a=s
Ifafunctionusesanemptyinterfaceasitsargumenttype,itcanacceptanytype;ifafunctionusesemptyinterfaceasitsreturnvaluetype,itcanreturnanytype.
Methodargumentsofaninterface
Anyvariablecanbeusedinaninterface.Sohowcanweusethisfeaturetopassanytypeofvariabletoafunction?
Forexampleweusefmt.Printlnalot,buthaveyouevernoticedthatitcanacceptanytypeofargument?Lookingattheopensourcecodeoffmt,weseethefollowingdefinition.
typeStringerinterface{
String()string
}
ThismeansanytypethatimplementsinterfaceStringercanbepassedtofmt.Printlnasanargument.Let'sproveit.
interface
60
packagemain
import(
"fmt"
"strconv"
)
typeHumanstruct{
namestring
ageint
phonestring
}
//Humanimplementedfmt.Stringer
func(hHuman)String()string{
return"Name:"+h.name+",Age:"+strconv.Itoa(h.age)+"years,Contact:"+h.
phone
}
funcmain(){
Bob:=Human{"Bob",39,"000-7777-XXX"}
fmt.Println("ThisHumanis:",Bob)
}
LookingbacktotheexampleofBox,youwillfindthatColorimplementsinterfaceStringeraswell,soweareabletocustomizetheprintformat.Ifwedon'timplementthisinterface,fmt.Printlnprintsthetypewithitsdefaultformat.
fmt.Println("Thebiggestoneis",boxes.BiggestsColor().String())
fmt.Println("Thebiggestoneis",boxes.BiggestsColor())
Attention:Ifthetypeimplementedtheinterfaceerror,fmtwillcallerror(),soyoudon'thavetoimplementStringeratthispoint.
Typeofvariableinaninterface
Ifavariableisthetypethatimplementsaninterface,weknowthatanyothertypethatimplementsthesameinterfacecanbeassignedtothisvariable.Thequestionishowcanweknowthespecifictypestoredintheinterface.TherearetwowayswhichIwillshowyou.
AssertionofComma-okpattern
Gohasthesyntaxvalue,ok:=element.(T).Thischeckstoseeifthevariableisthetypethatweexpect,where"value"isthevalueofthevariable,"ok"isavariableofbooleantype,"element"istheinterfacevariableandtheTisthetypeofassertion.
interface
61
Iftheelementisthetypethatweexpect,okwillbetrue,falseotherwise.
Let'suseanexampletoseemoreclearly.
packagemain
import(
"fmt"
"strconv"
)
typeElementinterface{}
typeList[]Element
typePersonstruct{
namestring
ageint
}
func(pPerson)String()string{
return"(name:"+p.name+"-age:"+strconv.Itoa(p.age)+"years)"
}
funcmain(){
list:=make(List,3)
list[0]=1//anint
list[1]="Hello"//astring
list[2]=Person{"Dennis",70}
forindex,element:=rangelist{
ifvalue,ok:=element.(int);ok{
fmt.Printf("list[%d]isanintanditsvalueis%d\n",index,value)
}elseifvalue,ok:=element.(string);ok{
fmt.Printf("list[%d]isastringanditsvalueis%s\n",index,value)
}elseifvalue,ok:=element.(Person);ok{
fmt.Printf("list[%d]isaPersonanditsvalueis%s\n",index,value)
}else{
fmt.Printf("list[%d]isofadifferenttype\n",index)
}
}
}
It'squiteeasytousethispattern,butifwehavemanytypestotest,we'dbetteruseswitch.
switchtest
Let'suseswitchtorewritetheaboveexample.
interface
62
packagemain
import(
"fmt"
"strconv"
)
typeElementinterface{}
typeList[]Element
typePersonstruct{
namestring
ageint
}
func(pPerson)String()string{
return"(name:"+p.name+"-age:"+strconv.Itoa(p.age)+"years)"
}
funcmain(){
list:=make(List,3)
list[0]=1//anint
list[1]="Hello"//astring
list[2]=Person{"Dennis",70}
forindex,element:=rangelist{
switchvalue:=element.(type){
caseint:
fmt.Printf("list[%d]isanintanditsvalueis%d\n",index,value)
casestring:
fmt.Printf("list[%d]isastringanditsvalueis%s\n",index,value)
casePerson:
fmt.Printf("list[%d]isaPersonanditsvalueis%s\n",index,value)
default:
fmt.Println("list[%d]isofadifferenttype",index)
}
}
}
Onethingyoushouldrememberisthatelement.(type)cannotbeusedoutsideoftheswitchbody,whichmeansinthatcaseyouhavetousethecomma-okpattern.
Embeddedinterfaces
ThemostbeautifulthingisthatGohasalotofbuilt-inlogicsyntax,suchasanonymousfieldsinstruct.Notsuprisingly,wecanuseinterfacesasanonymousfieldsaswell,butwecallthemEmbeddedinterfaces.Here,wefollowthesamerulesasanonymousfields.More
interface
63
specifically,ifaninterfacehasanotherinterfaceembeddedwithinit,itwillbehaveasifithasallthemethodsthattheembeddedinterfacehas.
Wecanseethatthesourcefileincontainer/heaphasthefollowingdefinition:
typeInterfaceinterface{
sort.Interface//embeddedsort.Interface
Push(xinterface{})//aPushmethodtopushelementsintotheheap
Pop()interface{}//aPopmethodthatpopselementsfromtheheap
}
Weseethatsort.Interfaceisanembeddedinterface,sotheaboveInterfacehasthethreemethodscontainedwithinthesort.Interfaceimplicitly.
typeInterfaceinterface{
//Lenisthenumberofelementsinthecollection.
Len()int
//Lessreturnswhethertheelementwithindexishouldsort
//beforetheelementwithindexj.
Less(i,jint)bool
//Swapswapstheelementswithindexesiandj.
Swap(i,jint)
}
Anotherexampleistheio.ReadWriterinpackageio.
//io.ReadWriter
typeReadWriterinterface{
Reader
Writer
}
Reflection
ReflectioninGoisusedfordetermininginformationatruntime.Weusethereflectpackage,andthisofficialarticleexplainshowreflectworksinGo.
Therearethreestepsinvolvedwhenusingreflect.First,weneedtoconvertaninterfacetoreflecttypes(reflect.Typeorreflect.Value,thisdependsonthesituation).
t:=reflect.TypeOf(i)//getmeta-dataintypei,andusettogetallelements
v:=reflect.ValueOf(i)//getactualvalueintypei,andusevtochangeitsvalue
Afterthat,wecanconvertthereflectedtypestogetthevaluesthatweneed.
interface
64
varxfloat64=3.4
v:=reflect.ValueOf(x)
fmt.Println("type:",v.Type())
fmt.Println("kindisfloat64:",v.Kind()==reflect.Float64)
fmt.Println("value:",v.Float())
Finally,ifwewanttochangethevaluesofthereflectedtypes,weneedtomakeitmodifiable.Asdiscussedearlier,thereisadifferencebetweenpassbyvalueandpassbyreference.Thefollowingcodewillnotcompile.
varxfloat64=3.4
v:=reflect.ValueOf(x)
v.SetFloat(7.1)
Instead,wemustusethefollowingcodetochangethevaluesfromreflecttypes.
varxfloat64=3.4
p:=reflect.ValueOf(&x)
v:=p.Elem()
v.SetFloat(7.1)
Wehavejustdiscussedthebasicsofreflection,howeveryoumustpracticemoreinordertounderstandmore.
interface
65
ConcurrencyItissaidthatGoistheClanguageofthe21stcentury.Ithinktherearetworeasons:first,Goisasimplelanguage;second,concurrencyisahottopicintoday'sworld,andGosupportsthisfeatureatthelanguagelevel.
goroutinegoroutinesandconcurrencyarebuiltintothecoredesignofGo.They'resimilartothreadsbutworkdifferently.Morethanadozengoroutinesmaybeonlyhave5or6underlyingthreads.Goalsogivesyoufullsupporttosharingmemoryinyourgoroutines.Onegoroutineusuallyuses4~5KBofstackmemory.Therefore,it'snothardtorunthousandsofgoroutinesonasinglecomputer.Agoroutineismorelightweight,moreefficientandmoreconvenientthansystemthreads.
goroutinesrunonthethreadmanageratruntimeinGo.Weusethegokeywordtocreateanewgoroutine,whichisafunctionattheunderlyinglevel(main()isagoroutine).
gohello(a,b,c)
Let'sseeanexample.
packagemain
import(
"fmt"
"runtime"
)
funcsay(sstring){
fori:=0;i<5;i++{
runtime.Gosched()
fmt.Println(s)
}
}
funcmain(){
gosay("world")//createanewgoroutine
say("hello")//currentgoroutine
}
Output:
Concurrency
66
hello
world
hello
world
hello
world
hello
world
hello
Weseethatit'sveryeasytouseconcurrencyinGobyusingthekeywordgo.Intheaboveexample,thesetwogoroutinessharesomememory,butwewouldbetterofffollowingthedesignrecipe:Don'tuseshareddatatocommunicate,usecommunicationtosharedata.
runtime.Gosched()meanslettheCPUexecuteothergoroutines,andcomebackatsomepoint.
Thescheduleronlyusesonethreadtorunallgoroutines,whichmeansitonlyimplementsconcurrency.IfyouwanttousemoreCPUcoresinordertotakeadvantageofparallelprocessing,youhavetocallruntime.GOMAXPROCS(n)tosetthenumberofcoresyouwanttouse.Ifn<1,itchangesnothing.Thisfunctionmayberemovedinthefuture,seemoredetailsaboutparallelprocessingandconcurrencyinthisarticle.
channelsgoroutinesruninthesamememoryaddressspace,soyouhavetomaintainsynchronizationwhenyouwanttoaccesssharedmemory.Howdoyoucommunicatebetweendifferentgoroutines?Gousesaverygoodcommunicationmechanismcalledchannel.channelislikeatwo-waypipelineinUnixshells:usechanneltosendorreceivedata.Theonlydatatypethatcanbeusedinchannelsisthetypechannelandthekeywordchan.Beawarethatyouhavetousemaketocreateanewchannel.
ci:=make(chanint)
cs:=make(chanstring)
cf:=make(chaninterface{})
channelusestheoperator<-tosendorreceivedata.
ch<-v//sendvtochannelch.
v:=<-ch//receivedatafromch,andassigntov
Let'sseemoreexamples.
Concurrency
67
packagemain
import"fmt"
funcsum(a[]int,cchanint){
total:=0
for_,v:=rangea{
total+=v
}
c<-total//sendtotaltoc
}
funcmain(){
a:=[]int{7,2,8,-9,4,0}
c:=make(chanint)
gosum(a[:len(a)/2],c)
gosum(a[len(a)/2:],c)
x,y:=<-c,<-c//receivefromc
fmt.Println(x,y,x+y)
}
Sendingandreceivingdatainchannelsblocksbydefault,soit'smucheasiertousesynchronousgoroutines.WhatImeanbyblockisthatagoroutinewillnotcontinuewhenreceivingdatafromanemptychannel,i.e(value:=<-ch),untilothergoroutinessenddatatothischannel.Ontheotherhand,thegoroutinewillnotcontinueuntilthedataitsendstoachannel,i.e(ch<-5),isreceived.
BufferedchannelsIintroducednon-bufferedchannelsabove.Goalsohasbufferedchannelsthatcanstoremorethanasingleelement.Forexample,ch:=make(chanbool,4),herewecreateachannelthatcanstore4booleanelements.Sointhischannel,weareabletosend4elementsintoitwithoutblocking,butthegoroutinewillbeblockedwhenyoutrytosendafifthelementandnogoroutinereceivesit.
ch:=make(chantype,n)
n==0!non-buffer(block)
n>0!buffer(non-blockuntilnelementsinthechannel)
Youcantrythefollowingcodeonyourcomputerandchangesomevalues.
Concurrency
68
packagemain
import"fmt"
funcmain(){
c:=make(chanint,2)//change2to1willhaveruntimeerror,but3isfine
c<-1
c<-2
fmt.Println(<-c)
fmt.Println(<-c)
}
RangeandCloseWecanuserangetooperateonbufferchannelsasinsliceandmap.
packagemain
import(
"fmt"
)
funcfibonacci(nint,cchanint){
x,y:=1,1
fori:=0;i<n;i++{
c<-x
x,y=y,x+y
}
close(c)
}
funcmain(){
c:=make(chanint,10)
gofibonacci(cap(c),c)
fori:=rangec{
fmt.Println(i)
}
}
fori:=rangecwillnotstopreadingdatafromchanneluntilthechannelisclosed.Weusethekeywordclosetoclosethechannelinaboveexample.It'simpossibletosendorreceivedataonaclosedchannel;youcanusev,ok:=<-chtotestifachannelisclosed.Ifokreturnsfalse,itmeansthethereisnodatainthatchannelanditwasclosed.
Remembertoalwaysclosechannelsinproducersandnotinconsumers,orit'sveryeasytogetintopanicstatus.
Concurrency
69
Anotherthingyouneedtorememberisthatchannelsarenotlikefiles.Youdon'thavetoclosethemfrequentlyunlessyouaresurethechanneliscompletelyuseless,oryouwanttoexitrangeloops.
SelectIntheaboveexamples,weonlyuseonechannel,buthowcanwedealwithmorethanonechannel?Gohasakeywordcalledselecttolistentomanychannels.
selectisblockingbydefaultanditcontinuestoexecuteonlywhenoneofchannelshasdatatosendorreceive.Ifseveralchannelsarereadytouseatthesametime,selectchooseswhichtoexecuterandomly.
packagemain
import"fmt"
funcfibonacci(c,quitchanint){
x,y:=1,1
for{
select{
casec<-x:
x,y=y,x+y
case<-quit:
fmt.Println("quit")
return
}
}
}
funcmain(){
c:=make(chanint)
quit:=make(chanint)
gofunc(){
fori:=0;i<10;i++{
fmt.Println(<-c)
}
quit<-0
}()
fibonacci(c,quit)
}
selecthasadefaultcaseaswell,justlikeswitch.Whenallthechannelsarenotreadyforuse,itexecutesthedefaultcase(itdoesn'twaitforthechannelanymore).
Concurrency
70
select{
casei:=<-c:
//usei
default:
//executesherewhencisblocked
}
TimeoutSometimesagoroutinebecomesblocked.Howcanweavoidthistopreventthewholeprogramfromblocking?It'ssimple,wecansetatimeoutintheselect.
funcmain(){
c:=make(chanint)
o:=make(chanbool)
gofunc(){
for{
select{
casev:=<-c:
println(v)
case<-time.After(5*time.Second):
println("timeout")
o<-true
break
}
}
}()
<-o
}
RuntimegoroutineThepackageruntimehassomefunctionsfordealingwithgoroutines.
runtime.Goexit()
Exitsthecurrentgoroutine,butdeferedfunctionswillbeexecutedasusual.
runtime.Gosched()
Letstheschedulerexecuteothergoroutinesandcomesbackatsomepoint.
runtime.NumCPU()int
ReturnsthenumberofCPUcores
Concurrency
71
runtime.NumGoroutine()int
Returnsthenumberofgoroutines
runtime.GOMAXPROCS(nint)int
SetshowmanyCPUcoresyouwanttouse
Concurrency
72
2.8SummaryInthischapter,wemainlyintroducedthe25Gokeywords.Let'sreviewwhattheyareandwhattheydo.
breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar
varandconstareusedtodefinevariablesandconstants.packageandimportareforpackageuse.funcisusedtodefinefunctionsandmethods.returnisusedtoreturnvaluesinfunctionsormethods.deferisusedtodefinedeferfunctions.goisusedtostartanewgoroutine.selectisusedtoswitchovermultiplechannelsforcommunication.interfaceisusedtodefineinterfaces.structisusedtodefinespecialcustomizedtypes.break,case,continue,for,fallthrough,else,if,switch,gotoanddefaultwereintroducedinsection2.3.chanisthetypeofchannelforcommunicationamonggoroutines.typeisusedtodefinecustomizedtypes.mapisusedtodefinemapwhichissimilartohashtablesinotherlanguages.rangeisusedforreadingdatafromslice,mapandchannel.
Ifyouunderstandhowtousethese25keywords,you'velearnedalotofGoalready.
Summary
73
GoProgrammingBasics
Workspace
$GOPATHand$GOROOT$GOROOT:ThisisanenvironmentvariablewhichstoresthepathwhereyourGoinstallationispresentifyouhavecustomizedit.$GOPATH:TheGouniverseforyourmachine.TheideaisthatallyourGocodeshouldresideinadirectorytreesothecodeisn'tlyingaroundinrandomplaces.
InmostunixsystemsitisdonewithexportGOPATH=/usr/home/suraj/Go.
My$GOPATHis/usr/home/suraj/Go,ithasthefollowingstructure
pkg:Thelibrarieswhich'llbeimportedinourcode,thereisa.afilecreatedattherespectivepath.bin:ThebinaryfileofourprojectwillbeinstalledhereoncallingGoinstallintheprojectfoldersrc:Willholdtheactualcode,ifyouhaveagithubaccount,thenyoucancreateafoldertreeinsidelike
Go
srcgithub.com
thewhitetulipwsharepicsort
golang.netsourcegraph.com
bin(binaries)wsharepicsort
pkg
GotakesthisuniqueapproachsothatalltheGocodeiswellorganizedandnotthrowninahaphazardmanner.Thismakesiteasytolocatecodeforhumansandsoftwarealike.
Packages
General
74
Packages,inGo,arejustoneormorefile(s)whichcontainsGosourcecode.Itcanbenamedanything,butitneedstobeainafolderandeachfileinthepackageshouldhavethelinepackage<name>atthetop.Thefoldernamehastobeexactlysameasthe<name>wespecifiyineachpackagesourcefile.
Therecanbeanynumberoffilesinapackagedirectory,butonlyonefilewiththemainpackage.Whilebuildingthecode,thecompilerstartswiththemainpackage.
MVCPatterns
WhenIstartedlearningbuildingwebappsinDjango,IfirstreadabouttheMVCpattern,themodels,theviewsandwhatnot.ButthatknowledgewastobeassumedbymeandIhadnoideawhythatdecisionwasmade,alongwiththatDjangoworkslikemagic,onehastoadapttoit'squirks.HencewhileprogramminginGo,it'llbeimmenselybenefitialtogrowuptotheMVCpatternbystartingoutsmall.Thefirstappshouldentirelyresideinthemain.gofile,thenslowly,astheappgrows,itneedstobestructuredwell,sotheallthehandlersgointoaviewspackage,templatesgoinatemplatesfolder,databaserelatedentitiesgoinanotherpackage.
Learningbydoingandexperimentingisthebestway.Challengethestatusquo.
PackagenamingTheactualpackagenameisjust"views"or"views",thepackageisfoundoutbythecompilerfromit'sabsolutepathfromthe$GOPATH/srcdirectory.FortheTasksapp,itresidesin$GOPATH/src/github/thewhitetulip/Tasks,thus,mypackageswillliewithintheTasksfolder.Thismakesiteasyfordistributingthelibraries.Theoretically,onecancreateapackagedirectlyinthe$GOPATH/srcfoldertoskipoutthelongnamingconventions,butdoingsobreaksthecodedistributionviaversioncontrol.
Whenpackagesareresolved,bytheGocompiler,itfirstlooksinthestandardlibraryandthenstartslookingforthefullnameinthe$GOPATH/srcfolder.
Internaldeployment
We'llfollowthestandardpracticeandputourcodein$GOPATH/src/github.com/thewhitetulip/Tasksfolder.Whiletestingourapp,weneedadeploymentversion.IuseedTaskswhielbuildingTasks,Ihavemadeafolder~/TasksandhereIkeepthedeploymentversionofTasks,whichcontainsthebinaryversionandthestaticfiles.Everynewbuildinthesrcgetspushedinthisfolder.
General
75
Runningaserver
WedeploywebappsonIPaddressesonspecificports.Typicallywechoosealargeportnumberlike8000soitdoesn'taffectsomeotherapplications.Certainportsarerestrictedandneedsudoaccess,like80.
TheIPaddresswebindtheservertocanbeapublicorprivate.Publicmeansusing":8080"or"0.0.0.0:8080".Privatemeans"127.0.0.1:8080"
Publicmeansanycomputerinthesamenetworkcanaccessthewebapp.Privatemeansonlyyourcomputerwillbeabletoaccessit.
//Public
log.Fatal(http.ListenAndServe(":8080",nil))
//Private
log.Fatal(http.ListenAndServe("127.0.0.1:8080",nil))
Note:127.0.0.1
127.0.0.1,calledlocalhost,isaspecialIPaddresswhichisgiventoeverymachinetorefertoitself.itdoesn'tmatterifyouareconnectedtothenetworkornot,yourmachinewillalwayshavethisIPaddress,soifyouarewantprivacyuse127.0.0.1.
Ifyoudowantyourwebapplicationtobeaccessiblethenuse0.0.0.0.0:8080orjust:8080,whenyougivejusttheportnumber,theGolanguageassumestheIPaddressthemachineisonbutnot127.0.0.1.
General
76
WebProgrammingBasicsWebserversareprogramswhichgetanHTTPrequestandsendbackanHTTPresponsetorequests,theformatoftherequestistheHTTPprotocol.
TheGolanguagehashttpsupportinit'sstandardlibraryasnet/http.
Initially,HTTPwasbuiltfortransferringplaintext,lateritallowedmultimediacontenttoo.
Whenwetypewww.github.comonourbrowser'saddressbar
1. Browseraddseitherhttps://orhttp://andatrailingforwardslash2. OurrequestbecomesHTTPGET/github.com.3. WesendouttheHTTPGETrequest4. Github'sserverswillprocesstherequestandsendbackaresponse.5. Ourbrowserwillrenderthepage.
BeforeAJAXwasinvented,youhadtorefreshthepageeachtime.WithAJAX,wecanmodifythepage'scontentswithoutrefresingthepage.
HTTPMethods
HTTPhasvariousmethodslikeGET,POST,DELETE,PUT.
GET:usedtoretrievetheURL,GET/willgetthehomepage.POST:usedtocreatedatastoredontheURL.PUT:usedtoupdatedataontheURL.DELETE:usedtodeletedataintheURL.
//Createanewcategory.
//POST/categories
//Updateanexistingcategory.
//PUT/categories/12
//Viewthedetailsofacategory.
//GET/categories/12
//Deleteanexistingcategory.
//DELETE/categories/12
Thinkofcategoriesasadocument,POSTtocreateit,GETtofetchit,PUTtoupdateit,andDELETEtodeleteit.Fordeletingatask,ratherthansendingGET/delete/1234,weshouldsendaDELETE/tasks/1234.Thus,ratherthanoverusingtheGETmethodforeverything,
WebProgrammingBasics
77
wecanreallyusecontextbasedonthemethodoftherequest.
GETvsPOST
Apartfromtheirfunctionaldifferences,GETandPOSTdifferinsecurityperspectives.
GETtransfersdataviatheURL.POSTsendsdataintherequest'sbodyorpayload,butthatisn'thiddenorencryptedbydefault,butitisn'tvisibleontheURL,itiseasilyaccessibletoanyonewhoknowshowtoreadaHTTPrequest.
Securityissomethingyoubuildyourapplicationaround.Inshortthereisn'tmuchdifferencebetweenGETandPOSTwhenweconsidersecurity,bothtransferdatainplaintextGETisjustrelativelyalittlelesssecuresinceURLsareloggedbyaproxyserver/firewall/browserhistoryandthatGETrequestscanbedonebythebrowseronbehalfoftheuserwithoutconfirmation.Botsarecommonovertheinternet,botscanvisitrandomlytoeverylinkpresentinyourapplication,buttheydon'tsendrandomdatatoanyFormyouhave,orifso,veryfewbotscandothat.
Forprotectingdataofthewebapp,onehastosticktousingHTTPSandsanitizeanydatathatcomesfromtheuser.
Example
Ablogconsistsofacollectionofposts,aposthastags,iswrittenbysomeauthor,atsometimeandhassomeprimarykeytouniquelyidentifyitinourdatabaseandithasaslugwhichmeanstheURL.
Thisistheeraofsemanticweb,thusthenewbeautifulURLslike,surajblog.com/posts/welcome-the-new-year,theslugisthewelcome-the-new-year.
WhentheservergetsaHTTPGETrequestof/posts/welcome-the-new-year,it'llsearchforURLhandlersstartingwiththelistofURLhandlerswehavegiven,thenit'llfindtheclosestmatch,inourcaseit'llbe/post/,thenit'llcallthehandlerofthisURL.
Our/rootURLshouldbeattheverybottomofourlist.Becausewhileexecuting,checksaredonefromtoptobottom.
//samplehandlerdefinition
http.HandleFunc("/post/",ShowPostBySlug)
http.HandleFunc("/",ShowAllPosts)
Handlertalktothedatabase,fetchthedataandrendertemplateswhichshowupasHTMLpagesinourbrowser.
WebProgrammingBasics
78
Whatisatemplate?
Templatesareawaytopresentdatatotheuser.TheserverpopulatedthetemplatesandsendstheHTMLpagebacktothebrowser.Forablog,itdoesn'tmakesensetogeneratehtmlpageforeachpost.Thisiswhythereisaposttemplateandtheserverwillgetallthedetailslikecontent,title,datepublishedandpopulatetheposttemplateandreturnitbacktothebrowser.
AwebapplicationisbasicallyawayofrepresentingdatastoredinthedatabasetotheenduserusingHTTP.
Writingawebapplication:
1. Fixthedatabasestructure.2. UnderstandhowdataflowsanddecidetheURLs3. Writetemplatestocorrespondingto(almost)eachURL4. WritefunctionsinGotohandleeachURLpattern,calledhandlers.5. Handlersfetchdatafromthedatabaseandpopulatedatainthetemplates.
Notabusingtemplates
Thelogicbehindcreatingtemplateswastonottorepeatourhtmlpage.Templatesupportvariableswhichourhandlersaregoingtopopulate.Thestandardwayistohandlethebusinesslogininsidethehandlerandusetemplatesjustforrenderingthedata.Templatesaretobeusedonlyforthepresentationlogicnotthebusinesslogic.Itbecomesdifficulttomaintainapplicationswhichhavebusinesslogicinsidethetemplate.Itshouldneverbedone.
Example:Wearegoingtobuildatodolistmanagerinthisbook.Itsupportmultipleusers.
Wrongway:Fetchalltasksinthetemplateandonlyshowthoseofthecurrentuser.i.e.filterthetasksinthetemplateCorrectway:Fetchonlythetasksbelongingtothecurrentuser.i.e.filterthetasksinthehandler.
FunctionalityofourEditTaskURLwhichis/edit/<id>.
fileviews/addViews.go
WebProgrammingBasics
79
//EditTaskFuncisourhandlerwhichwillhandlethe/edit/<id>URL
funcEditTaskFunc(whttp.ResponseWriter,r*http.Request){
//Code
task:=db.GetTaskByID(id)
editTemplate.Execute(w,task)
//Code
}
filedb/tasks.go
funcGetTaskByID(idint)types.Context{
//Codetofetchtasksofthecurrentuser
context:=types.Context{Tasks:tasks}
returncontext
}
TheEditTaskFunctalkstothedatabasewiththeGetTaskByIDfunctionandfetchesthetasksforthecurrentuserandpopulatestheeditTemplate.
Thuswecansplitanapplicationintoviews,database,templatesandcontroller(mainpackage).
StaticFiles
Staticfilesarerequiredfortemplates,theyarealltheCSS/JS/Imageswhichweloadintoourhtmltemplates.
TheURLwillbe/static/.
Execution:
1. Wegetarequestlike/static/<filepath>2. Wegotothepublicdirectoryofourapplicationandlookfor3. Ifwegetafileofthatpaththenweservethefile,othewisesenda404error.
Thepublicfoldercontainsallyourstaticfiles.Wewillhaveatemplatesfolderonthesamefolderwherethepublicispresent.
Thereasontemplatesisaseparatefolderisthatitisaseparateentityandshouldn'tbepubliclyavailableusingthe/static/URL.
WebProgrammingBasics
80
public
||--static
|||--css
|||`--styles.css
..andmore
||`--js
|||--bootstrap.min.js
||....andmore
templates
||--completed.html
||...andmore
NoteOutput
Theaboveoutputisofthetreeprogram.
WebProgrammingBasics
81
BasicwebapplicationMakeafolderinyour$GOPATH/src/github.com/<yourname>/Taskssubstituteyourusernameinlieuof<yourname>.
Createafilemain.go
filemain.go
packagemain
import(
"log"
"net/http"
)
funcmain(){
PORT:="127.0.0.1:8080"
log.Fatal(http.ListenAndServe(PORT,nil))
}
Weimportthehttppackageinourapplicationwithimportnet/http.
Nowgotoyourterminalandtype
[Tasks]$gorunmain.go
Youwillnoticethattheprogramdoesn'tprintanythingbecausewetolditonlytolistenontheport.Ifwewanttousertoknowthatwearerunningaserveronthatport,weshouldprintamessagesayingso,asshowninthebelowexample.
codeexamplefile:3.1basicServer.go
Implementation
82
packagemain
import(
"log"
"net/http"
)
funcmain(){
PORT:=":8080"
log.Print("Runningserveron"+PORT)
log.Fatal(http.ListenAndServe(PORT,nil))
}
Wheneverwerunthis,we'llgetthebelowoutput
2016/01/0122:00:36Runningserveron:8080
Openlocalhost:8080inabrowserandyou'llgetthemessage"404pagenotfound"
Thisisbecauseasofnowwehavejuststartedaservertolistenontheport8080,butwehaven'thandledtheURL.TheHTTP404errorwillbedisplayedeachtimetheserverwon'tbeabletofigureouthowtoservetherequest.
HandlingURLs
http.HandleFunc("/complete/",ShowCompleteTasksFunc)
//ShowCompleteTasksFuncisusedtopopulatethe"/completed/"URL
funcShowCompleteTasksFunc(whttp.ResponseWriter,r*http.Request){
}
WeHandleFuncinthenet/httppackagetohandleURLs.ThefirstargumentistheURLtobehandledandthesecondparameteristhefunction.Wecandefinefunctionsinthesecondparameter,butyouareadvisedagainstit.
Thehandlerfunctionrequirestwoarguments,aResponseWriterobjectandaRequestobject.WearegoingtowritetotheResponseWriterdependingonwhatwegetintheRequestobject.
HandlerExample
WewanttowritetheURLthattheuserisvisitingonourwebapp.TheURLcanbefoundintherequestobject,r.URL.Path.
Implementation
83
packagemain
import(
"log"
"net/http"
)
funcmain(){
PORT:=":8080"
log.Print("Runningserveron"+PORT)
http.HandleFunc("/",CompleteTaskFunc)
log.Fatal(http.ListenAndServe(PORT,nil))
}
funcCompleteTaskFunc(whttp.ResponseWriter,r*http.Request){
w.Write([]byte(r.URL.Path))
}
Parameterizedrouting
WhenwegettheURL/tasks/124,wewanttogetthetasknumber124,wedothefollowing
//GetTaskFuncisusedtodeleteatask,
funcGetTaskFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
id:=r.URL.Path[len("/tasks/"):]
w.Write([]byte("Getthetask"+id))
}
WetakeasubstringoftheURLandremovethe/delete/partandwehavetheIDofthetask.
Thisexamplemakesuseoftheslicingconcept.Itissimple,ifPathisourstringvariablethenPath[1:]isthesubstringwhichincludeseverythingfromthefirstcharacter,indexbeingzero.
Note:Parameterizedrouting
IdeallyweshouldcheckingtheURLpathinsideourview,wearesupposedtousearouter.Forrealprojects,youshouldusearouter.Herewearelearning,sowewon'tbeusingarouter.
Servingstaticfiles
http.Handle("/static/",http.FileServer(http.Dir("public")))
Implementation
84
Forservingstaticfiles,weusetheFileServermethodofthehttppackage.Ittakesafolderasanargument.Makesureyougiveonlythepublicfolderpathintheargument.
Homework
Readthedocumentationofnet/http&logandgettoknowofthemethods/functionsinthepackages[1]FindouthowmanyalternativesaretheretoListenAndServe.Writeahandlertoservestaticfiles,iffileisavailableonthatpaththenitshouldservethefile,otherwiseitmustreturnanHTTP404error.Writeacommandlineapplicationtosharefiles/folderoverHTTP,thesyntaxshouldbelikethis./wshare-ffile.pdffileonlink=192.168.2.1:8080.
Footnotes
[1]:Learnhowtoreaddocumentation,itsavesalotoftime.
Implementation
85
DesigningourwebappThedesignphaseisthemostimportantinanysoftwareprojectasonesmallmistakeindesigningcoststheprojectserverly.Itbecomesverycostlytofixthatlater.
Ourwebappisgoingtobeatodolistmanagerforasingleuser,soit'llhavethefollowingfeatures:
1.Abilitytoadd/delete/modifytask.
2.Abilitytomarktasksascomplete/incomplete.
3.Displaythetasksasaonecolumnlist.
4.Abilitytoswitchmodesbetweenpending/completed/deletedtasks
5.Displaynotificationslikenoteadded/deleted/archived
6.Searchfortextandhighlightthetextinthesearchresultspage.
TheDesign
TranslatingourdesigntoAPI,wegetthefollowing.
/add/POST=addnewtask
/GET=showpendingtasks
/complete/GET=showcompletedtasks
/deleted/GET=showdeletedtasks
/edit/<id>POST=editpost
/edit/<id>GET=showtheeditpage
/trash/<id>POST=trashposttorecyclebin
/delete/<id>POST=permanentlydeletepost
/complete/<id>POST=markpostascomplete
/login/POST=dothelogin
/login/GET=showloginpage
/logout/POST=logtheuserout
/restore/<id>POST=restorethattask
/update/<id>POST=updatetask
/change/GET=willallowchangingpassword
/register/GET=showtheregisterpage
/register/POST=willaddentriesintodatabase
file~/main/main.go
Designingourwebapp
86
packagemain
import(
"log"
"net/http"
)
funcmain(){
http.HandleFunc("/complete/",CompleteTaskFunc)
http.HandleFunc("/delete/",DeleteTaskFunc)
http.HandleFunc("/deleted/",ShowTrashTaskFunc)
http.HandleFunc("/trash/",TrashTaskFunc)
http.HandleFunc("/edit/",EditTaskFunc)
http.HandleFunc("/completed/",ShowCompleteTasksFunc)
http.HandleFunc("/restore/",RestoreTaskFunc)
http.HandleFunc("/add/",AddTaskFunc)
http.HandleFunc("/update/",UpdateTaskFunc)
http.HandleFunc("/search/",SearchTaskFunc)
http.HandleFunc("/login",GetLogin)
http.HandleFunc("/register",PostRegister)
http.HandleFunc("/admin",HandleAdmin)
http.HandleFunc("/add_user",PostAddUser)
http.HandleFunc("/change",PostChange)
http.HandleFunc("/logout",HandleLogout)
http.HandleFunc("/",ShowAllTasksFunc)
http.Handle("/static/",http.FileServer(http.Dir("public")))
log.Print("runningonport8080")
log.Fatal(http.ListenAndServe(":8080",nil))
}
Createallfunctionswementionedaboveandmakethenecessarychangesasperonedefinitionthatweshowbelowinthisfile.
funcShowAllTasksFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
message:="allpendingtasksGET"
}else{
message:="allpendingtasksPOST"
}
w.Write([]byte(message))
}
Afteryoucreatethesefunctionsruntheserverasbelow,
[Tasks]$gobuild
[Tasks]$./Tasks
Designingourwebapp
87
Inyourbrowsertypelocalhost:8080andtypealltheseURLsandseewhatmessageyouget.
Homework
Checkthedocumentationforhttp.ResponseWriterandhttp.Requestobjectsandgettoknowallthevariables/functions/constantsforhttppackageandthesetwowhichwementioned
Designingourwebapp
88
UsingdatabasesinGoGodoesn'tprovideoutoftheboxsupportforanydatabase,butitprovidesaninterface,whichcanbeusedbydatabaselibrarycreatorstokeepallthedatabaselibrariescompatiblewitheachother.
Wewillusesqliteforthisbook.
Creatingandconfiguringdatabase
UsethisDDLtocreateatableinourdatabase
[Tasks]$sqlite3tasks.db
SQLiteversion3.8.22013-12-0614:53:30
Enter".help"forinstructions
EnterSQLstatementsterminatedwitha";"
sqlite>CREATETABLEtask(
idintegerprimarykeyautoincrement,
titlevarchar(100),
contenttext,
is_deletedchar(1)default'N',
created_datetimestamp,
last_modified_attimestamp,
finish_datetimestamp
);
Usethefollowinginsertstatementstoenterdatainourtable,sowe'llbeginreadingdatainourShowAllTasksfunctionwhichwewroteinthepreviouschapter
DatabaseHandling
89
INSERTINTO"task"VALUES(1,'Publishongithub',
'Publishthesourceoftasksandpicsortongithub',
'N','2015-11-1215:30:59','2015-11-2114:19:22',
'2015-11-1717:02:18');
INSERTINTO"task"VALUES(4,'gofmtall',
'Theideaistorungofmt-wfile.goonevery
gofileinthelisting,*Editturnsoutthisisisdifficult
todoingolang**Editbarely3linebashscript:P'
,'N','2015-11-1216:58:31',
'2015-11-1410:42:14','2015-11-1313:16:48');
INSERTINTO"task"VALUES(7,'modificationstotask',
'1.addsearchfunction#Done
2.apriorityfeaturetotask
3.commentsontasks
4.Duedate
5.removedependencyonhttprouter#Done',
'N','2015-11-1304:23:27','2015-11-1304:23:27',NULL);
Installingthesqlitedriverforgo
We'llusethego-sqlite3drivercreatedbymattn.Thereasonbeingitimplementsthedatabase/sqlinterface.Typethisinyourterminal:
goget"github.com/mattn/go-sqlite3"
Accessingdatabaseingo
Weimportthelibraryasimport_"github.com/mattn/go-sqlite3"
Thepackageisimportedanonymously,whenweimportnet/httpwehavetousehttp.toaccessthepackage.Tobeabletoswapunderlyingdatabases,wewon'tdothat.Thustheanonymouspackageimport.
Underthehood,thedriverregistersitselftothedatabase/sqlpackage.
NoteThe'_'operator
The_operatorhasauniqueroleingo,itisusedtodenoteanemptyvariable,usedwhenwewanttoignoredata.numsisanarray,whileweloopthrougharraysusingrangeitreturnsa(key,value)pair.hereweignorethekeyandprintthevalue.
DatabaseHandling
90
for_,value:=rangenums{
fmt.Println(value)
}
Alsonotethatwecangiveapackagealiasinsteadoftheunderscorecharactershouldwewantanaliasforourpackage.
Everydatabasehasaconnectionmechanism,fileforsqliteandIPaddressforMySQL/Postgres.
Weencapsulateourdbobjectinsideastruct.Wealsoencapsulatethedatabaseactionsasshownbelow
vardatabaseDatabase
//Databaseencapsulatesdatabase
typeDatabasestruct{
db*sql.DB
}
func(dbDatabase)begin()(tx*sql.Tx){
tx,err:=db.db.Begin()
iferr!=nil{
log.Println(err)
returnnil
}
returntx
}
func(dbDatabase)prepare(qstring)(stmt*sql.Stmt){
stmt,err:=db.db.Prepare(q)
iferr!=nil{
log.Println(err)
returnnil
}
returnstmt
}
func(dbDatabase)query(qstring,
args...interface{})(rows*sql.Rows){
rows,err:=db.db.Query(q,args...)
iferr!=nil{
log.Println(err)
returnnil
}
returnrows
}
funcinit(){
database.db,err=
DatabaseHandling
91
sql.Open("sqlite3","./newtask.db")
iferr!=nil{
log.Fatal(err)
}
}
//Closedatabaseconnection
funcClose(){
database.db.Close()
}
//taskQueryencapsulatesExec()
functaskQuery(sqlstring,args...interface{})error{
SQL:=database.prepare(sql)
tx:=database.begin()
_,err=tx.Stmt(SQL).Exec(args...)
iferr!=nil{
log.Println("taskQuery:",err)
tx.Rollback()
}else{
tx.Commit()
}
returnerr
}
Noteinit()
Theinitfunctionisthefirstfunctiontorunwhenthepackageisimportedorexecuted.Thisiswhywedotheinitializationinit.
NoteDBfromgodoc
typeDBstruct{
//containsfilteredorunexportedfields
}
DBisadatabasehandlerepresentingapoolofzeroormoreunderlyingconnections.It'ssafeforconcurrentusebymultiplegoroutines.
Thesqlpackagecreatesandfreesconnectionsautomatically;italsomaintainsafreepoolofidleconnections.Ifthedatabasehasaconceptofper-connectionstate,suchstatecanonlybereliablyobservedwithinatransaction.Wedonotwantthisvariabletobeaccessiblefromoutsidethepackagesoitisunexported.Openmayjustvalidateitsargumentswithoutcreatingaconnectiontothedatabase.Toverifythatthedatasourcenameisvalid,callPing().ThereturnedDBissafeforconcurrentusebymultiplegoroutinesandmaintainsitsownpoolofidleconnections.Thus,theOpenfunctionshouldbecalledjustonce.ItisrarelynecessarytocloseaDB.
DatabaseHandling
92
err=db.Ping()
iferr!=nil{
//dosomethingaboutit
}
Thesql.DBobjectshoudn'tbeopenedandclosedfrequently,itshouldbeclosedonlywhenwenolongerneedtoaccessthedatabase.
Pleaserefertothedocumentationofdatabase/sqlformoredetails.
Noteexportedandunexportedvariables
Exported:variablestartswithacapitalletterandisaccessibleforthosewhoimportthispackageUnexported:variableisprivateforthepackage,notaccessibleoutsideit'sdeclaration.
Queryingthedatabase
Statementsthatdon’treturnrowsshouldnotuseQueryfunctions;theyshoulduseExec().
Fromthegodocumentation
func(*DB)Exec
func(db*DB)Exec(querystring,args...interface{})(Result,error)
Execexecutesaquerywithoutreturninganyrows.Theargsareforany
placeholderparametersinthequery.
WeusetheQuerymethodtoquerythedatabasewhenweexpectsomeresultfromthedatabase.
DatabaseHandling
93
getTaskSQL="selectid,title,content,created_datefromtask
wherefinish_dateisnullandis_deleted='N'orderbycreated_dateasc"
rows,err:=database.Query(getTaskSQL)
iferr!=nil{
log.Println(err)
}
deferrows.Close()
forrows.Next(){
err:=rows.Scan(&TaskID,&TaskTitle,&TaskContent,&TaskCreated)
TaskContent=strings.Replace(TaskContent,"\n","<br>",-1)
iferr!=nil{
log.Println(err)
}
fmt.Println(TaskID,TaskTitle,TaskContent,TaskCreated)
}
taskSQL:="deletefromtask"
tx:=database.begin()
_,err=tx.Stmt(SQL).Exec(args...)
iferr!=nil{
tx.Rollback()
}else{
tx.Commit()
}
Notedeferkeyword
Weusedeferinsideafunctioncall.
packagemain
import(
"fmt"
"io/ioutil"
"os"
)
funcmain(){
file,err:=os.Open('file.dat')
iferr!=nil{
fmt.Println("Filedoesn'texistoryoudon'thave
readpermission")
}
deferfile.Close()
inputReader:=bufio.NewReader(file)
//dosomethingaboutinputReader
}
DatabaseHandling
94
Thedeferstatementputsthefunctioncallatthebottomofthecallstack,sowheneverthefunctionreturns,deferistriggered.Onehastobecarefulwithusingdefer,itcancausedifficulttofindbugs.
file~/main/main.go
Findandfixthebug:
packagemain
import(
_"github.com/mattn/go-sqlite3"
"fmt"
)
vardatabase*sql.DB
funcinit(){
deferdatabase.Close()
database,err=sql.Open("sqlite3","./tasks.db")
iferr!=nil{
fmt.Println(err)
}
}
//intentionalbugexists,fixit
funcmain(){
getTaskSQL="selectid,title,content,created_datefromtask
wherefinish_dateisnullandis_deleted='N'orderbycreated_dateasc"
rows,err:=database.Query(getTaskSQL)
iferr!=nil{
fmt.Println(err)
}
deferrows.Close()
forrows.Next(){
err:=rows.Scan(&TaskID,&TaskTitle,&TaskContent,&TaskCreated)
TaskContent=strings.Replace(TaskContent,"\n","<br>",-1)
iferr!=nil{
fmt.Println(err)
}
fmt.Println(TaskID,TaskTitle,TaskContent,TaskCreated)
}
err=rows.Err()
iferr!=nil{
log.Fatal(err)
}
}
Alwaysdeferrows.Close(),tofreethedatabaseconnectioninthepool.Solongasrowscontainstheresultset,thedatabaseconnectionisinuseandnotavailableintheconnectionpool.
DatabaseHandling
95
Whentherows.Next()functionreturnsEOF(EndofFile),whichmeansthatithasreachedtheendofrecords,it'llcallrows.Close()foryou,Close()canbecalledmultipletimeswithoutsideeffects.
Single-RowQueries
Ifaqueryreturnsatmostonerow,youcanuseashortcut:
vartaskDescriptionstring
query:="selecttaskDescriptionfromtaskwhereid=?"
err=db.QueryRow(query,1).Scan(&taskDescription)
iferr!=nil{
log.Fatal(err)
}
fmt.Println(taskDescription)
ErrorsfromthequeryaredeferreduntilScan()iscalled,andthenarereturnedfromthat.YoucanalsocallQueryRow()onapreparedstatement:
query:="selecttaskDescriptionfromtaskwhereid=?"
stmt,err:=db.Prepare(query,1).Scan(&taskDescription)
iferr!=nil{
log.Fatal(err)
}
...
vartaskDescriptionstring
err=stmt.QueryRow(1).Scan(&taskDescription)
iferr!=nil{
log.Fatal(err)
}
fmt.Println(taskDescription)
Writingdataintothedatabase
Abovefunctionsfetcheddatafromthedatabase,transactionsaretobeusedtowritedatainthedatabase.
Belowliesanexampleofusingtransaction
filedb/db.go
DatabaseHandling
96
//RestoreTaskisusedtorestoretasksfromtheTrash
funcRestoreTask(idint)error{
query:="updatetasksetis_deleted='N',last_modified_at=datetime()whereid=?"
restoreSQL,err:=database.Prepare(query)
iferr!=nil{
fmt.Println(err)
}
tx,err:=database.Begin()
iferr!=nil{
fmt.Println(err)
}
_,err=tx.Stmt(restoreSQL).Exec(id)
iferr!=nil{
fmt.Println("doingrollback")
tx.Rollback()
}else{
tx.Commit()
}
returnerr
}
Prepare
Preparecreatesapreparedstatementforlaterqueriesorexecutions.Multiplequeriesorexecutionsmayberunconcurrentlyfromthereturnedstatement.Thecallermustcallthestatement'sClosemethodwhenthestatementisnolongerneeded.
NoteErrorhandling
BecauseofGo'suniquemethodofhandlingerrors,wecanbestuckintheiferr!=nilland.But,ontheotherhanditsimplifieserrorhandlingaswejusthavetoreturntheerrormessage.
AnexcellentresourceaboutGoanddatabasescanbefoundathttp://go-database-sql.org
Thefaultinourcode:
Fixingtheintentionalbugintheabovecode:
funcinit(){
deferdatabase.Close()
database,err=sql.Open("sqlite3","./tasks.db")
iferr!=nil{
fmt.Println(err)
}
}
DatabaseHandling
97
Homework
Seethe/code/chapter-4/4.5databaseinourcoderepositoryandmodifythefiletoinsertdatafromthe4.3formsuploadfolder.Wehavetwoworkingcodeset,oneofprintingformvaluesontheconsoleandoneoffetchingdbvaluesandrenderingatemplate.Whatyouhavetodoisbasedonthischapter,writemethodstoinsertvaluesfromtheformtothedatabase.
DatabaseHandling
98
AnExampleExpectedoutput:
openyourtask.dbfileinsqlite3likethis
[Tasks]$sqlite3task.db
sqlite>selecttitlefromtasklimit1;
Publishongithub
Nowthisoutputshouldmatchwiththeoneweseeatlocalhost:8080
Afterrunningthefile,gotolocalhost:8080andlocalhost:8080/add
file~/main/main.go
packagemain
import(
"database/sql"
"fmt"
_"github.com/mattn/go-sqlite3"
"log"
"net/http"
"time"
)
vardatabase*sql.DB
varerrerror
//Taskisthestructusedtoidentifytasks
typeTaskstruct{
Idint
Titlestring
Contentstring
Createdstring
}
//Contextisthestructpassedtotemplates
typeContextstruct{
Tasks[]Task
Navigationstring
Searchstring
Messagestring
}
funcinit(){
WebappExample
99
database,err=sql.Open("sqlite3","./tasks.db")
iferr!=nil{
fmt.Println(err)
}
}
funcmain(){
http.HandleFunc("/",ShowAllTasksFunc)
http.HandleFunc("/add/",AddTaskFunc)
fmt.Println("runningon8080")
log.Fatal(http.ListenAndServe(":8080",nil))
}
//ShowAllTasksFuncisusedtohandlethe"/"URLwhichisthedefaultone
funcShowAllTasksFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
context:=GetTasks()//truewhenyouwantnondeletednotes
w.Write([]byte(context.Tasks[0].Title))
}else{
http.Redirect(w,r,"/",http.StatusFound)
}
}
funcGetTasks()Context{
vartask[]Task
varcontextContext
varTaskIDint
varTaskTitlestring
varTaskContentstring
varTaskCreatedtime.Time
vargetTasksqlstring
getTasksql="selectid,title,content,created_datefromtask;"
rows,err:=database.Query(getTasksql)
iferr!=nil{
fmt.Println(err)
}
deferrows.Close()
forrows.Next(){
err:=rows.Scan(&TaskID,&TaskTitle,&TaskContent,&TaskCreated)
iferr!=nil{
fmt.Println(err)
}
TaskCreated=TaskCreated.Local()
a:=Task{Id:TaskID,Title:TaskTitle,Content:TaskContent,
Created:TaskCreated.Format(time.UnixDate)[0:20]}
task=append(task,a)
}
context=Context{Tasks:task}
returncontext
}
WebappExample
100
//AddTaskFuncisusedtohandletheadditionofnewtask,"/add"URL
funcAddTaskFunc(whttp.ResponseWriter,r*http.Request){
title:="randomtitle"
content:="randomcontent"
truth:=AddTask(title,content)
iftruth!=nil{
log.Fatal("Erroraddingtask")
}
w.Write([]byte("Addedtask"))
}
//AddTaskisusedtoaddthetaskinthedatabase
funcAddTask(title,contentstring)error{
query:="insertintotask(title,content,created_date,last_modified_at)\
values(?,?,datetime(),datetime())"
restoreSQL,err:=database.Prepare(query)
iferr!=nil{
fmt.Println(err)
}
tx,err:=database.Begin()
_,err=tx.Stmt(restoreSQL).Exec(title,content)
iferr!=nil{
fmt.Println(err)
tx.Rollback()
}else{
log.Print("insertsuccessful")
tx.Commit()
}
returnerr
}
HomeworkThehomeworkistosplitthecodeintopackagesandgetittowork,thetypedefinitiongoesintothetypes/types.gofile,thehandlerdefinitiongoesintotheviews/views.go,thedatabasereadandwritemethodsgointothedb/db.go.Makesurethatafteryourefactorthecode,thatthecoderuns.
WebappExample
101
WorkingwithFormsHTMLformsareusedtogetdatafromtheuser.FormscanuseboththeGETandPOSTmethodsfortransferringdatatotheserver,butitisrecommendedtouseHTTPPOSTmethodjustbecauseitdoesn'thighlightdataintheURLandbecauseofthemanythingswediscussedinthechapterWebProgrammingBasics.
Formsarerenderedbytemplating,whichwe'llseeinalaterchapter.Asofnowwewanttounderstandhowtogetdatafromtheenduserbyusingforms.
Therearetwopartsofworkingwithforms,theHTMLpartandtheGopart.TheHTMLpagegetsthedataandsendsittotheserverasaPOST/GETandtheGopartwillparsetheformtodosometasklikelettingtheusertologinorinsertingdatainthedatabase.
Eachformelementhasanamewhichisreferencedintheserversidepartoftheform,belowwehavethefileuploadandadropdownlist.Bothofwhichhaveauniquename.
<formaction="/add/"method="POST">
<divclass="form-group">
<inputtype="text"name="title"class="form-control"id="add-note-title"place
holder="Title"
style="border:none;border-bottom:1pxsolidgray;box-shadow:none;">
</div>
<divclass="form-group">
<textareaclass="form-control"name="content"id="add-note-content"placeholde
r="Content"
rows="10"style="border:none;border-bottom:1pxsolidgray;box-shadow:none;"><
/textarea>
File:<inputtype="file"name="uploadfile"/><br>
Priority:<selectname="priority">
<option>---</option>
<optionvalue="3">High</option>
<optionvalue="2">Medium</option>
<optionvalue="1">Low</option>
</select>
</div>
</div>
<divclass="modal-footer">
<buttontype="button"class="btnbtn-default"data-dismiss="modal">Close</butt
on>
<inputtype="submit"text="submit"class="btnbtn-default"/>
</div>
</form>
Formhandling
102
WhenwearepopulatingthisHTMLpage,wecanalsopopulateitdynamically,withoutgettingboggeddownbysyntax,ignoretemplatingforthewhileWehaveavariablecalledNavigationandonecalledCategories,weloopthroughCategoriesandiftheNavigationisequaltothatcategorythenthecheckedvalueistrue.
Category:
<selectname="category"class="dropdown">
<option>---</option>
{{$navigation:=.Navigation}}{{$categories:=.Categories}}
{{range$cat:=$categories}}
<optionvalue="{{$cat.Name}}"{{ifeq$cat.Name$navigation}}checked="checke
d"{{end}}>{{$cat.Name}}</option>
{{end}}
</select>
Herewehaveusedadropdownbox,youcanuseradiobuttonslikebelow
<inputtype="radio"name="gender"value="1">Female
<inputtype="radio"name="gender"value="2">Male
<inputtype="radio"name="gender"value="3">Other
Orcheckboxes
<inputtype="checkbox"name="gender"value="female">Female
<inputtype="checkbox"name="gender"value="male">Male
<inputtype="checkbox"name="gender"value="other">Other
Wegetthevalueofadropdownbox/radiobutton/checkboxontheserversidebyusingthenamefieldlikebelow:
value:=r.Form.Get("gender")
value:=r.FormValue("gender")
Aswesawearlier,ourwebserversbasicallytakeaHTTPRequestobjectandreturnanHTTPResponseObject,belowisanexampleofasampleHTTPRequestobject.
ThehostistheIPaddresssendingthereq,UserAgent:fingerprintofthemachine,theAccept-fieldsdefinevariouspartslikethelanguage,encodingRefereriswhichIPmadethecall,CookieisthevalueofthecookiestoredonthesystemandConnectionisthetypeofconnection.
Inthisrequestsnapshot,wealsohaveafilewhichweupload,forfileupload,thecontenttypeismultipart/form-data
Formhandling
103
RequestHeader
Host:127.0.0.1:8081
User-Agent:...
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language:en-US,en;q=0.5
Accept-Encoding:gzip,deflate
DNT:1
Referer:http://127.0.0.1:8081/
Cookie:csrftoken=abcd
Connection:keep-alive
RequestBody
Content-Type:multipart/form-data;
boundary=---------------------------6299264802312704731507948053
Content-Length:15031
-----------------------------6299264802312704731507948053
Content-Disposition:form-data;name="title"
workingwithforms
-----------------------------6299264802312704731507948053
Content-Disposition:form-data;name="CSRFToken"
abcd
-----------------------------6299264802312704731507948053
Content-Disposition:form-data;name="content"
finishthechapterworkingwithforms
-----------------------------6299264802312704731507948053
Content-Disposition:form-data;name="uploadfile";filename="2.4workingwithform.md"
Content-Type:text/x-markdown
--filecontent--
-----------------------------6299264802312704731507948053
Content-Disposition:form-data;name="priority"
3
-----------------------------6299264802312704731507948053--
IfyouhadwonderedhowGoogle'shomepageshowsapopupwhenyouvisitgoogle.comonIEofFirefox,itchecksyourUser-Agent.ThethingwithHTTPrequestisthattheycanbemodifiedtoanyextent,theChromedevelopertoolsgivesyouquitesophisticatedtoolstomodifyyourrequests,evenUserAgentspoofingisadefaultfeatureavaiable,thisfeatureisavailablesowecantestourwebappsinonewindowsimulatingmanyinternetbrowsersatonego.
Formhandling
104
Thebasicpartofworkingwithformsistoidentifywhichuserthatparticularformbelongsto,therearewaystoattainthat,wecaneitherhaveastatefulorastatelesswebserver.
Astatelessserverdoesn'tstoresessions,itrequiresanauthenticationkeyforeachrequestwhileastatefulserverstoressessions.Forstoringsessionsacookieisused,whichisafilewhichisstoredintheprivatememoryofthewebbrowserwhichweuse.Onlythewebsitewhichcreatedthecookiecanaccessthecookie,nothirdpartywebsitescanaccessthecookies,buttheOSusercanread/edit/deletecookiesusingthewebbrowser.
CSRF
CSRFstandsforCrossRequestSiteForgery.AnywebsitecansendaPOSTrequesttoyourwebserver,whosenttherequestcanbefoundintheRefererfieldofyourHTTPresponse.Itisaformofconfuseddeputyattackinwhichthedeputyisyourwebbrowser.Amalicioususerdoesn'thavedirectaccesstoyourwebsite,soitmakesuseofyourbrowsertosendamaliciousrequest.Typicallycookiesenableyourbrowsertoauthenticateitselftoawebserver,sowhatthesemaliciouswebsitesdois,theysendinaHTTPrequestonbehalfofyourbrowser.
Wecanthwartthisattackbyrestrictingthereferertoyourowndomain,butitisquiteeasytomanipulatethemisspeltrefererfieldofaHTTPrequest.
Anotherwayistousetokens.Whilerenderingourform,wesendinahiddenfieldwithcryptogeneratedstringof256characters,sowhenweprocessthePOSTrequest,wefirstcheckifthetokenisvalidornotandthendecideifthedatacamefromagenuinesourceorfromamalicioussource.Itdoesn'thavetobemaliciousactually,evenifalegitimateusertriedtotrickyourwebserverintoacceptingdata,weshouldn'tentertainit.
Tocheckthecsrftoken,weservethetokentotheformandstoreitinacookie,whenwegetthePOSTrequest,wecheckifbothareequalornot.Thisisbecauseamaliciouspersonmighttrickausertoclickonaformbuttheycan'tsetcookiesforyourwebapplication.
Apointtonotehereisthatnevertrustuserdata.Alwaysclean/sanitizedatawhichyougetfromtheenduser.
NoteJavascript
Ifyouareseriousaboutwebdevelopment,yououghttolearnJavascriptindetail.Whilebuildingawebapp,therewillbetimeswhenyouwouldwanttoimprovetheUIofyourapplication,whichwouldmeanachangeinthehtmlpage.UsingJSisinevitablewhilebuildingbeautifulwebapps,whileaddingsomenewhtmlfeature,openthe"webinspector"presentinthedevelopertoolsanddynamicallyaddthehtmlcode.ThewebinspectorallowsustomanipulatetheCSSandHTMLpart.Nowopenthejavascriptconsole,thatenables
Formhandling
105
youtotesttheJSfeaturewhichyouarewillingtoadd.Forinstance,inthetasksapplication,therewasnoprovisiontoexpand/contractthesizeofthetask,soIaddedabuttoninthewebinspector,
<buttonclass="toggle"></button>
IntheJSconsole,totogglethevisibilityofmy.noteContentfield,Ididthis:
$('.toggle').next().toggle()
Thisprovedthatitworks,sonowgotoyourtemplateandactuallyaddthecode.Makesurethehtmliscorrectbecausewhilerunning,thehtmlfilesareparsedonce,soforanyhtmlchange,youhavetorunthewebappforeachHTMLchange.Sooncethehtmlissetup,ifyouchangetheJS/CSSthenyoujusthavetorefreshthepage,becausethehtmlpagegetstheJS/CSSeachtimethepageisloaded.
Aswesawintheaboveparagraph,forpreventingCSRF,weneedtogenerateatokenandsendasahiddenfieldintheformandstoreitinacookie,whenwegetthePOSTrequestfromthe
FormsinGo
Inthebelowfunctionwesetthecookie,wefirstgenerateaCSRFtoken,which'llbeuniqueforeachHTTPrequestwhichwegetandstoreitinacookie.
//ShowAllTasksFuncisusedtohandlethe"/"URLwhichisthedefaultons
funcShowAllTasksFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
context:=db.GetTasks("pending")//truewhenyouwantnondeletednotes
ifmessage!=""{
context.Message=message
}
context.CSRFToken="abcd"
message=""
expiration:=time.Now().Add(365*24*time.Hour)
cookie:=http.Cookie{Name:"csrftoken",Value:"abcd",Expires:expiration}
http.SetCookie(w,&cookie)
homeTemplate.Execute(w,context)
}else{
message="Methodnotallowed"
http.Redirect(w,r,"/",http.StatusFound)
}
}
Formhandling
106
ThebelowhandlerhandlesthePOSTrequestsentbyourform,itfetchesthevalueofthecsrftokencookieandgetsthevalueofthehiddenCSRFTokenfieldoftheaddtaskform.Ifthevalueofthecookieisequaltothevaluefetchedbytheform,thenweallowittogotothedatabase.
ThecalltoParseFormwillparsethecontentsoftheformintoGettablefieldswhichwecanfetchusingtheGetfunction.Thiscalliscompulsory.
//AddTaskFuncisusedtohandletheadditionofnewtask,"/add"URL
funcAddTaskFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="POST"{
r.ParseForm()
file,handler,err:=r.FormFile("uploadfile")
iferr!=nil{
log.Println(err)
}
taskPriority,priorityErr:=strconv.Atoi(r.FormValue("priority"))
ifpriorityErr!=nil{
log.Print("unabletoconvertprioritytointeger")
}
priorityList:=[]int{1,2,3}
for_,priority:=rangepriorityList{
iftaskPriority!=priority{
log.Println("incorrectprioritysent")
//mightwanttologassecurityincident
taskPriority=1//thisdefaultstheprioritytolow
}
}
title:=template.HTMLEscapeString(r.Form.Get("title"))
content:=template.HTMLEscapeString(r.Form.Get("content"))
formToken:=template.HTMLEscapeString(r.Form.Get("CSRFToken"))
cookie,_:=r.Cookie("csrftoken")
ifformToken==cookie.Value{
ifhandler!=nil{
r.ParseMultipartForm(32<<20)//definedmaximumsizeoffile
deferfile.Close()
f,err:=os.OpenFile("./files/"+handler.Filename,os.O_WRONLY|os.O_CR
EATE,0666)
iferr!=nil{
log.Println(err)
return
}
deferf.Close()
io.Copy(f,file)
filelink:=
"<br><ahref=/files/"+handler.Filename+">"+handler.Filename+"
</a>"
content=content+filelink
}
Formhandling
107
truth:=db.AddTask(title,content,taskPriority)
iftruth!=nil{
message="Erroraddingtask"
log.Println("erroraddingtasktodb")
}else{
message="Taskadded"
log.Println("addedtasktodb")
}
http.Redirect(w,r,"/",http.StatusFound)
}else{
log.Fatal("CSRFmismatch")
}
}else{
message="Methodnotallowed"
http.Redirect(w,r,"/",http.StatusFound)
}
}
NoteCookies
Cookieisawaytostoredataonthebrowser,HTTPisastatelessprotocol,itwasn'tbuiltforsessions,basicallytheInternetitselfwasn'tbuiltconsideringsecurityinmindsinceinitiallyitwasjustawaytosharedocumentsonline,henceHTTPisstateless,whenthewebserverreceivesrequests,itcan'tdistinguishbetweentwoconsequitiverequests,hencetheconceptofcookieswereadded,thuswhilestartingasession,wegenerateasessionIDandstoreitonthedatabaseinmemoryoronthedatabaseandwestorethesameIDonacookieonthewebbrowserandwevalidatebothofthemtoauthenticatethem.
Wehavetonotethat,ifwesetanexpirydateforacookie,thenitisstoredonthefilesystemotherwiseitisstoredinmemoryinthebrowser.Intheincognitomode,thisisthecase,allthecookiesarestoredinmemoryandnotinthefilesystem.
HTTPRequest
Host:localhost:8080
User-Agent:.....
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language:en-US,en;q=0.5
Accept-Encoding:gzip,deflate
DNT:1
Referer:http://localhost:8080/
Cookie:csrftoken=abcd
Connection:keep-alive
Cache-Control:max-age=0
Formhandling
108
Thebrowser,whilesendingaresponseappendsallthecookiedatastoredinitsmemoryorfilealongwiththeotheraspectsoftheHTTPrequestsowecanaccessthecookieasr.Cookie,it'llcontaineverycookieforthatparticulardomain,we'dthenloopthroughittofetchthedatawhichwewant.Thisisimportantforsecurityreasons,supposeIsetacookieonmyimaginedomain.comandifitcontainsacsrftokenandifthatcookieisaccessibletosomeotherwebappthenitishorriblesincetheycanmasqueradeasanylegitmateuser.Butthisain'tpossible,sinceawebsitecanonlyaccessthecookiestoredforitsowndomain,pluswehavethefeaturetosetHTTPonlyvalueofacookieastrue,whichsaysthatevenjavascriptcan'taccessthecookies.
HTTPResponse
Content-Type:text/html;charset=utf-8
Date:Tue,12Jan201616:43:53GMT
Set-Cookie:csrftoken=abcd;Expires=Wed,11Jan201716:43:53GMT
Transfer-Encoding:chunked
Whenwesetcookies,wewritethentotheHTTPresponsewhichwesendtotheclientandthebrowserreadstheCookieinformationandstorestheminthememoryorthefilesystemdependingontheExpiresfieldoftheresponse.
Fromthegodocumentation:
typeCookiestruct{
Namestring
Valuestring
Pathstring//optional
Domainstring//optional
Expirestime.Time//optional
RawExpiresstring//forreadingcookiesonly
//MaxAge=0meansno'Max-Age'attributespecified.
//MaxAge<0meansdeletecookienow,equivalently'Max-Age:0'
//MaxAge>0meansMax-Ageattributepresentandgiveninseconds
MaxAgeint
Securebool
HttpOnlybool
Rawstring
Unparsed[]string//Rawtextofunparsedattribute-valuepairs
}
InputValidation
Formhandling
109
Thebasicaspectofwebapplicationsisthatnodatacanbetrusted,eveniftheuserisn'tmaliciousherself,therearemanywaystotrickthebrowserintosendingHTTPrequestsandfoolingthewebservertorespondtowhatseemslikealegitimaterequest.Hencewehavetoverifyeverythingthatcomesfromtheuser.
Onemightargueherethatwedoallsortsofvalidationusingjavascript,buttherearewaystoevadeJSvalidations,thesimplestwayistodisableJSandmoresophisticatedwaysaretomanipulatetheHTTPrequestbeforethebrowsersendsit,itisliterallytrivialwhenweusejustthewebdevelopertoolsthatthesedaysbrowsersprovide.
ifformToken==cookie.Valueandtitle!=nilandcontent!=nil
Thetitleandcontentofthetaskismandatory,henceweaddedthenotnilpart.
Wedoinputvalidationwhenwedon'twantjunkdatatogointoourdatabase,supposetheuserhitthesubmitbuttontwice,orsomeotherscenario.Butwealsohavetoconsiderthecasewheretheuserwantstorunsomescriptonourwebsite,whichisdangerous,soweusethetemplate.HTMLEscapeStringmethodtoescapewhatmightruninmemoryofthecurrentbrowsersession.Eventhedatawhichcomesfromyourdropdownlistshouldbevalidated.
EverythinginawebformissentviaaRequestaswesawintheaboveexample,weuseadropdownlistwhenwehaveareexpectingaparticularsetofinputsbutforsomeonewhoknowswhatfirefoxdevtoolsare,caneasilymodifyanythingintherequest,forexamplewehavethepriorityasdropdownlistwemightthinkthatsincewehaveonlythreeentriesinthedropdownlist,shouldwekeepacheckinourviewhandlertonotacceptanythingbutthethreevalueswhicharepresentinourtemplate.Weoughttokeepachecklikebelow:
priorityList:=[]int{1,2,3}
for_,priority:=rangepriorityList{
iftaskPriority!=priority{
log.Println("incorrectprioritysent")
//mightwanttologassecurityincident
}
}
Thisisthepriorityfieldofourrequest
Content-Disposition:form-data;name="priority"
3
Formhandling
110
Allamalicioususerhastodoischangethisvalueandresendtherequest,theycanliterallyinsertanyvaluehere,justthinkwhatiftheysendrm-fr*.*andaccidentallyenoughthiscommandisexecutedonourserver.Thisalsobringsinanotheraspectofsecurity,neverrunyourmachineinrootmode,alwayskeeptherootmodeforadmintasksanduseanonrootmode.Evenifthatisn'tthecase,alessdangerousexamplewillbesendingahugenumberwiththerequest,assumingthatwehaveusedintegerasourvariable,theprogrammightcrashifithastohandleanumberbeyonditsstoragecapacity.Thismightbetermedasadenialofserviceattack.
Formhandling
111
UploadingfilesUploadingfilesisthenextstepinformprocessing,incaseoffiles,wesendtheentirefiledataintheHTTPheader,sowehavetosettheformencodingtoenctype="multipart/form-data".Thiswillinformourserverthatwearegoingtogetafilefromtheformalongwiththerestofthefields,ifany.
Thismeanswecangeteithereitherfile(s)anddataorjustfile(s)orjustdataandnofile(s).
AtthefirstlineofourHTTPhandler,wehavetowritethisline,ifthislineisnotpresentinthefirstlinethenitgivesunexpectedresults
file,handler,err:=r.FormFile("uploadfile")
ifhandler!=nil{
r.ParseMultipartForm(32<<20)//definedmaximumsizeoffile
deferfile.Close()
f,err:=os.OpenFile("./files/"+handler.Filename,os.O_WRONLY|os.O_CR
EATE,0666)
iferr!=nil{
log.Println(err)
return
}
deferf.Close()
io.Copy(f,file)
filelink:="<br><ahref=./files/"+handler.Filename+">"+handler.File
name+"</a>"
content=content+filelink
}
Wefirstprovidethemaximumsizeofthefilewhichis32^20,whichisgiganticforourwebapp,noteveryonehastheInternetinfrastructuretouploadthatbigafile,butwewanttobeflexible.
Webasicallygetafilefromtheformrequest,intheformhandlerweopenanotherfilewiththesame/differentnameandthenreadthefileformtherequestandwriteitontheserver.Weneedtohandlethescenewherewenamethefiledifferentlysowe'dneedtostoretheoldfilename->newfilenamerelationsomewhere,itcanbeadatabasetable.
Thefilenameshouldbescrubbed,sincetheusercangiveanymaliciousnametodamageourapplication.
Wenowwanttorandomizethefilenameofthefileswhichtheusersupload.InyourAddTaskFuncaddthefollowinglines
UploadingFiles
112
ifhandler!=nil{
r.ParseMultipartForm(32<<20)//definedmaximumsizeoffile
deferfile.Close()
randomFileName:=md5.New()
io.WriteString(randomFileName,strconv.FormatInt(time.Now().Unix(),10))
io.WriteString(randomFileName,handler.Filename)
token:=fmt.Sprintf("%x",randomFileName.Sum(nil))
f,err:=os.OpenFile("./files/"+token,os.O_WRONLY|os.O_CREATE,0666)
iferr!=nil{
log.Println(err)
return
}
deferf.Close()
io.Copy(f,file)
filelink:="<br><ahref=/files/"+token+">"+handler.Filename+"</a
>"
content=content+filelink
fileTruth:=db.AddFile(handler.Filename,token)
iffileTruth!=nil{
message="Erroraddingfilenameindb"
log.Println("erroraddingtasktodb")
}
}
file~/Tasks/db/db.go
//AddFileisusedtoaddthemd5ofafilenamewhichisuploadedtoourapplicat
ion
//thiswillenableustorandomizetheURLwithoutworryingaboutthefilenames
funcAddFile(fileName,tokenstring)error{
SQL,err:=database.Prepare("insertintofilesvalues(?,?)")
iferr!=nil{
log.Println(err)
}
tx,err:=database.Begin()
iferr!=nil{
log.Println(err)
}
_,err=tx.Stmt(SQL).Exec(fileName,token)
iferr!=nil{
log.Println(err)
tx.Rollback()
}else{
log.Println(tx.Commit())
}
returnerr
}
UploadingFiles
113
tablestructure
CREATETABLEfiles(namevarchar(1000)notnull,autoNamevarchar(255)notnull);
Theseblockofcodedothefollowingthings:
1. Createaversionofnameforeachuploadedfile2. Insertitindatabase3. Referencethefileas/files/<randomName>4. Fileisnowreferencedbyournameratherthantheusersuppliedname
Thenextparttodoisregisteringthe/files/handler.
file:~/Tasks/views/views.go
//UploadedFileHandlerisusedtohandletheuploadedfilerelatedrequests
funcUploadedFileHandler(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
log.Println("intothehandler")
token:=r.URL.Path[len("/files/"):]
//file,err:=db.GetFileName(token)
//iferr!=nil{
log.Println("servingfile./files/"+token)
http.ServeFile(w,r,"./files/"+token)
//}
}
}
UploadingFiles
114
Templatespackage:text/template
Inthefirstchapterwehadacursoryglanceovertheconceptoftemplates.Thischapterisdedicatedentirelytotemplates.Aswesaidpreviously,awebapplicationrespondstocertainURLsandgivesanhtmlpagetothebrowserwhichthebrowsertheninterpretsandshowstotheenduser.Thishtmlpagewhichissenttothebrowseriswhatiscalledtemplatesinthebackendforwehaveatemplatewhichstoressomevariables,andinrealtimedataisprovidedintothetemplatewhichmakesitacompletehtmlpage.
Let'stakeapracticalexample.Supposewearebuildingamicrobloggingsite.Wewouldstartwithcreatingthefrontendinhtml.OurmicrobloggingsitewillshowHiUserontherightcorner.
Inourstatichtmlwewritethis<p>HiUser</p>
Butifweservethispageonourwebserverit'llnotchangeanything,it'llshowHiUser,thenameoftheuserwon'tcomemagically,wehavetoputitintothepagesomehow,hereweuseavariablesoinGowe'dapproachthisbyusingavariablenamed{{.Name}}soourhtmlnowwillbe<p>Hi{{.Name}}</p>.The.ismandatory.
Now,thisisthelogicweapplytoallourhtmlpages,keepingsuch{{}}variableexpansionparametersandservingthevalueoftheparameterwhileexecutingthetemplate.Ifyouarewonderinghowwe'ddothat,thisisthesyntax
homeTemplate.Execute(w,context)
//Contextisthestructpassedtotemplates
typeContextstruct{
Tasks[]Task
Namestring
Searchstring
Messagestring
}
Thesearethethreepartsofusingtemplates,firstyouneedtocreatetypeslikewehavecreatedtheContexttype,thenweneedtoreadthetemplate,thenweneedtousethecomponentsofthattypeinourtemplatefile.Sowhatremainsnowispassinganobjectofthattypeduringtemplateexecution.
Everytemplaterequiresthecontextobjectbecausethatiswhatdefinesthedatatobepopulatedinthetemplate.
Templates
115
Readingtemplate:
templates,err=template.Must(template.ParseFiles(allFiles...))
Note:template.Must
Mustisahelperthatwrapsacalltoafunctionreturning(*Template,error)andpanicsiftheerrorisnon-nilWhereallFilesispopulatedasbelow:
varallFiles[]string
templatesDir:="./public/templates/"
files,err:=ioutil.ReadDir(templatesDir)
iferr!=nil{
fmt.Println("Errorreadingtemplatedir")
}
for_,file:=rangefiles{
filename:=file.Name()
ifstrings.HasSuffix(filename,".html"){
allFiles=append(allFiles,templatesDir+filename)
}
}
ForthesakeofdemonstrationofhowtoparsemultiplefileswehaveusedtheParseFilesmethodtoparseallthe.htmlfiles,youcanusetheParseGlobmethodwhichisavailableinthestandardlibrary.
template.Must(template.ParseGlob(templatesDir+"*.html"))
ThedefinitionofParseGlobis:funcParseGlob(patternstring)(*Template,error)
WehavetospecifythePatternfortheParseGlobfunction,butwehavepassedthepathandthepatternbecausejustpassingthepatternisuseless,weneedthepathwherethepatternwillbeappliedtofindthelistofallfilesmeetingthecriteria.
Note:
1. ...operator:allFilesisastringsliceandallFiles...passesthefunctionaparameterasastring.
2. ParseFilesperformance:
Thereisonepointtonotehereaboutperformanceinparsingfiles,typicallyatemplatefilewon'tchangeuntilthereissomemajorchangetothecodebasesoweshouldonlyparsethefilesonce,ratherthankeepthiscodeineachviewhandleranddoinga
Templates
116
template.ParseFiles("home.html")
template.Execute(w,context)
Thisblockofcodewillunnecessarilyreadthehtmlpageeachtimewhileservingtheresponseofarequest,whichmeansiftenpeopleareusingourbloggingsitethenforeachpagetheyvisitthecodewillreadthehtmlpage,butthereisnoneedtodothingsthisway,wecanreadthehtmlfilesonceatthestartandthenusetheLookupmethodofthetemplateclass,
homeTemplate=templates.Lookup("home.html")
homeTemplate.Execute(w,context)
SubtemplatingWelearnthowtopassdatatotemplatesanddisplayitinthehtmlpage,itsohappensthatalotofcodeisusedinalltemplatessupposethenavigationbarortheheader,thenweneednotwritethesamechunkeverywhere,wecancreateatemplatetostorethat,andusesubtemplating{{template"_head.html".}}
Here,wehaveidentifiedachunkofcodewhichwewanttoreplicateandputitin_head.html.Thenweputtheabovestatementineachtemplatefilewherewewishtohaveourheader.Thiswaytemplatesbecomealotsmalleranddon'tcontainreplicatedcodeeverywhere.Donotethatthe.beforethefirst}isintentionalanditmeansthatallthevariableswhichwerepassedtothecurrenttemplatearepassedtothesubtemplate.
Thesubtemplateswhichwecreatedependsonourrequirement,butthebasicpointbehinditisthatifwearegoingtorepeatablockofHTMLcodethenweshouldformitasatemplate.
NoteUsingSubTemplates
Themainpointtonoteoverhereisthatwhenwearegoingtouseourtemplatesorsubtemplates,allthosehtmlfilesneedtobeparsed.Thebasicpointintemplatingisthatwehaveavariablewhichstoresalltemplatesevenifwearen'tgoingtorefertothatdirectlyinourcodeusingtheLookupmethodonthetemplatevariable.Thelookupmethodtakesthenameofthetemplate.Whenthe{{template_head.html.}}isevaluated,itgoestoourtemplatevariableandtriestofindoutthetemplateparsedwiththeexactname,ifitisnotpresentthenitdoesn'tcomplainbydefault,weshoulduseMustmethodifwewantittocomplain.
Templates
117
Examplefileviews/views.go
packageviews
import(
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"text/template"
)
var(
homeTemplate*template.Template
deletedTemplate*template.Template
completedTemplate*template.Template
loginTemplate*template.Template
editTemplate*template.Template
searchTemplate*template.Template
templates*template.Template
messagestring
//messagewillstorethemessagetobeshownasnotification
errerror
)
//PopulateTemplatesisusedtoparsealltemplatespresentin
//thetemplatesfolder
funcPopulateTemplates(){
varallFiles[]string
templatesDir:="./public/templates/"
files,err:=ioutil.ReadDir(templatesDir)
iferr!=nil{
fmt.Println("Errorreadingtemplatedir")
}
for_,file:=rangefiles{
filename:=file.Name()
ifstrings.HasSuffix(filename,".html"){
allFiles=append(allFiles,templatesDir+filename)
}
}
iferr!=nil{
fmt.Println(err)
os.Exit(1)
}
templates,err=template.Must(template.ParseFiles(allFiles...))
//templates,err:=template.Must(template.ParseGlob(templatesDir+".html"
))
Templates
118
iferr!=nil{
fmt.Println(err)
os.Exit(1)
}
homeTemplate=templates.Lookup("home.html")
deletedTemplate=templates.Lookup("deleted.html")
editTemplate=templates.Lookup("edit.html")
searchTemplate=templates.Lookup("search.html")
completedTemplate=templates.Lookup("completed.html")
loginTemplate=templates.Lookup("login.html")
}
//ShowAllTasksFuncisusedtohandlethe"/"URL
//TODOaddhttp404error
funcShowAllTasksFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
context:=db.GetTasks("pending")
//truewhenyouwantnondeletednotes
ifmessage!=""{
context.Message=message
}
homeTemplate.Execute(w,context)
message=""
}else{
message="Methodnotallowed"
http.Redirect(w,r,"/",http.StatusFound)
}
}
Loopingthrougharrays
<divclass="timeline">
{{if.Tasks}}
{{range.Tasks}}
<divclass="note">
<pclass="noteHeading">{{.Title}}</p>
<hr>
<pclass="noteContent">{{.Content}}</p>
</div>
{{end}}
{{else}}
<divclass="note">
<pclass="noteHeading">NoTaskshere</p>
<pclass="notefooter">Createnewtask<button>here</button></p>
</div>
{{end}}
</div>
Templates
119
The{{if.Tasks}}blockchecksifthearrayisemptyornot,ifitisnotthenit'llgotothe{{.range.Tasks}}whichwillloopthroughthearray,thenthe{{.Title}}willaccessthetitleand{{.Content}}willaccesstheContentofthatparticularTaskinstanceandwe'llseeallthetasksasalist.
Templatevariables
Wehavethisscenario,wehavearangeofcategoriesinthenavigationdrawerandifwevisitthe/category/study,thenourcategorynameshouldbehighlightedinthenavigationdrawer.Wedothisbystoringthevalueofthe.Navigationfield-whichtellsifitisaEditpage/Trashpage/Categorypage
{{$nav:=.Navigation}}
{{range$index,$cat:=.Categories}}
<liclass="sidebar-item">
<ahref="/category/{{$cat.Name}}"{{ifeq$cat.Name$nav}}class="active"{{
end}}>
<spanclass="nav-item">{{$cat.Name}}</span><spanclass="badgepull-right
">{{$cat.Count}}</span></a>
</li>
{{end}}
Creatingvariables
{{$url:=""}}willcreateablankvariable,onehastonotthatallvariablesarepracticallystringsoncetheyarerendered.{{$url:=.Navigation}}willcreateanewvariableandinitializeitwiththevalueof.Navigation
Forunderstandingwhytemplatevariablesarerequired,weneedtogointotheaboveblockofcode,whenweareusingtherangeoperator,weareparsingthearrayandtherangeblockgetstheelementsoftheblockbydefault.
MyContexttypeis
typeContextstruct{
Tasks[]Task
Navigationstring
Searchstring
Messagestring
CSRFTokenstring
Categories[]CategoryCount
Refererstring
}
Templates
120
Hencewhenwedoa{{range.Categories}}wewillbeaccessingeachelementas{{.}}perloop.Ifweuseanyothervalidvariablehere,likethe.Navigation,thentheblocktriestofindthe.Navigationinside.Categories,whichobviouslyisn'tpresent.
Nowweneedtomakethepageawareofwhichcategoryitisshowing,shouldtheusergotothe/categories/page.
ThelogicbehindmakingthatpagecategoryawareisthatwecreateaCSSclasstomarkthatparticularcategoryasactive,butforthat,we'dneedtoaccesstheCategorynameandNavigationwithintherange.Categoriesblock,thuswecreatetwovariables,onetostorethecategorynamefromthe.Navigationvariableandusetheifstatementlike
{{ifeq$cat$nav}}class="active"{{end}}
Thiswillmarkonlythatparticularcategoryasactiveandnotallofthem.
Intemplatinglogictheoperatorisfirstandthenthetwooperands.
eq:equal,le:lessthanequal,ge:greaterthanequal
Youcanalsouseifelseclauselikebelow:
{{$url:=""}}
<divclass="navbar-header">
{{if.Search}}<aclass="navbar-brand">Resultsfor:{{.Search}}</a>{{else}}{{i
feq.Navigation"pending"}}
{{$url:=""}}{{elseifeq.Navigation"completed"}}{{$url:=""}}{{elseife
q.Navigation"deleted"}}
{{$url:=""}}{{elseifeq.Navigation"edit"}}{{$url:=""}}{{else}}{{$url:=
"/category"}}{{end}}
<pclass="navbar-brand"href="{{$url}}/{{.Navigation}}">
{{ifeq.Navigation"pending"}}Pending{{elseifeq.Navigation"completed"}
}Completed{{elseifeq.Navigation"deleted"}}Deleted
{{elseifeq.Navigation"edit"}}Edit{{else}}{{.Navigation}}{{end}}{{en
d}}
</p>
Herewehadsomecomplicatedstuff,ifourpageisasearchone,wehadtoshowResultsfor:<query>,pending,deleted,edit,completedforrespectiveand/category/ifweareinthecategory.SowedefinedanemptyURLandassignedtheURLvaluesaccordingtothecomplicatedifelsestructure.
Homework
Templates
121
1. Takethehtmlpagesfromhttp://github.com/thewhitetulip/omninoteswebandmodifythemtosuitourpurposesWewouldneedtocreateonetemplateeachfortheoneswementionedintheabovevariabledeclaration,usetemplatingasfaraspossibleandlatercheckyourresultswithhttp://github.com/thewhitetulip/Tasks,pleasedotheexerciseonyourownfirstandthenonlychecktheTasksrepository.
2. Implementasearchinterface.Takeaqueryasinput,searchtasksforthatqueryandreturnanhtmlpagewiththequeryhighlightedintheresultingpage.
Templates
122
AuthenticationAuthenticationisusedtoverifyiftheusershaveaccesstothatparticularpartofyourwebapplication.Forunderstandinghowtoimplementauthenticationweneedtounderstandwhathappensbehindthescenesofabrowser.Supposewerunabankwebapplication.Wewantonlyourlegitimateuserstoaccessourwebapplication.Wesetupaloginpageandprovideouruserswiththeirusernameandpasswordwhichtheycanusetovalidatetheirclaimtoourwebapp.
Whenwesubmittheloginform,thebrowsertakesourusername,passwordandsendsaPOSTrequesttothewebserver,whichagainrespondswithaHTTPredirectresponseandwearereturnedtoourbankdashboard.
TheHTTPprotocolisstateless,whichmeanseveryrequestisunique.Thereisnowayforidentifyingautomaticallyifarequestisrelatedtoanotherrequest.Thisbringsabouttheproblemofauthentication,howthencanwevalidateiftheusershaveaccesstoourwebapp?
WecansendtheusernamealongwitheachHTTPrequest,eitherintheURLviaaGETrequestorinthePOSTrequest.Butthisisinefficientsinceforeachrequest,thewebserverwouldneedtohitthedatabasetovalidatetheusername,alsothiswouldmeanweaksecuritysinceifIknowyourusername,Icanimpersonateyouprettyeasilyandthewebserverishelplesstoidentifythisimpersonation.
TosolvethisproblemsSessionswereinvented,sessionsneedtousecookiesonthebrowsertofunction.ThebasicideaisthattheservergeneratesasessionIDandstoresitinacookie.Withsubsequentrequests,thebrowserwillsendthesessionIDalongwiththerequest,thewebserverwillthencometoknowfromthatsessionIDiftherequestisafakeoneornot.Alsowegettoknowwhotheuserisfromthat.
Cookies
Cookies,aswesawinapreviouschaptercanbeusedtostoreakey,valuepair.WeusedacookietostoretheCSRFtoken,thecookiehadthenameasCSRFandvalueasthetoken.
Pleasedon'tconfusesessionswithcookies,becausesessionsaren'takey,valuepair.Sessionsareawayofworkingwithcookiesontheserverside.ThereisagapoftheentireInternetbetweensessionsandcookies.
UserAuthentication
123
Cookiesarestoredinourbrowsers,forsecurityreasonsweneedtoenablethe"isHTTPOnly"fieldofourcookies,soonlyourwebapplicationcanreadthecookie.Otherwiseanyonejavascriptapplicationcaneasilyreadourcookiedefeatingitspurpose,wemightaswellnotkeepanauthenticationmechanismforourwebapp.
FromthegodocumentationtypeCookiestruct{NamestringValuestring
Pathstring//optional
Domainstring//optional
Expirestime.Time//optional
RawExpiresstring//forreadingcookiesonly
//MaxAge=0meansno'Max-Age'attributespecified.
//MaxAge<0meansdeletecookienow,equivalently'Max-Age:0'
//MaxAge>0meansMax-Ageattributepresentandgiveninseconds
MaxAgeint
Securebool
HttpOnlybool
Rawstring
Unparsed[]string//Rawtextofunparsedattribute-valuepairs
}
Thedomainofourcookieenablesarestrictedaccesstoourcookie.Avisitorgoestoourfictionalbankwebsite,sbank.comandentersausernameandpassword,acookieisstoredinthebrowserwhichonlythesbank.comdomaincanaccesssincewearesecurityawareandwehavesettheHttpOnlyfieldtotruewhilesettingthecookie.Thismeanssomemaliciouswebsitewhichissetupbyanattackertointentionallytargetourbankisn'tabletoaccessthecookieusingjavascript.
Onehastorememberthatcookieisnothingbutafilestoredinauser'sbrowser,ifcanbeaccessedoverHTTPbyourwebserver,aclientbrowserdoesallowaccesstoitthroughbrowsersettingsorcustomjavascript.Thebrowser,afterallisaplatform,andwehaveAPIstothatplatform.
Sessions
Asessionisaseriesofactionsperformedbetweenyouandthewebappyouareacting,enabledbythebrowserandtheInternetyouareusing.Whilegeneratinganewsession,weneedtocheckifasessionsisalreadyactive,ifsowereturnthesamesessionIDratherthancreateanewone,ifnot,wegenerateanewsessionID.SessionIDsneedtobesufficientlyrandom.Ofcoursewecan'tgeneratesomethingtotallyrandom,butwehavetoensuretogeneratesomethingthatnobodyelsecanreplicate,unlesstheyhaveaccesstoourprivatekeywhichweusetogenerateourrandomnumber.
UserAuthentication
124
Sessionhandlingusinggorilla/sessions
Tillnowweneverusedanythirdpartylibraryoraframeworkinthisbook,thisisforthefirsttimethatwearedoingso,asperthearrogantpeopleonHNwebetteruselibrariesforhandlingsessions,sincesecurityisthe#1aspectofanywebapplication,untilthetimeweareaspecialistinwebdevelopmentandcanwriteourownsessionmoduleitiswiserandsafertousingpre-builtpackages
Path:~/Tasks/sessions/sessions.go
packagesessions
import(
"net/http"
"github.com/gorilla/sessions"
)
//Storethecookiestorewhichisgoingtostoresessiondatainthecookie
varStore=sessions.NewCookieStore([]byte("secret-password"))
//IsLoggedInwillcheckiftheuserhasanactivesessionandreturnTrue
funcIsLoggedIn(r*http.Request)bool{
session,_:=Store.Get(r,"session")
ifsession.Values["loggedin"]=="true"{
returntrue
}
returnfalse
}
Thisisthesessionspackagewhichwewilluseinourapplication.
WecreateaCookieStorewhichstoresthesessionsinformationunderthe"sessions"inthebrowser.WegetthesessionIDstoredunderthesessioncookieandstoreitthesessionvariable.Whenitcomestousingthisfunction,wehavetheAddCommentFuncviewbelowwhichisgoingtohandlethecommentingfeatureofourapplication,it'llfirstcheckiftheuserhasanactivesessionandifso,it'llhandlethePOSTrequesttoaddacomment,ifnot,it'llredirecttheusertotheloginpage.
Path:~/Tasks/Views/addViews.go
UserAuthentication
125
//AddCommentFuncwillbeused
funcAddCommentFunc(whttp.ResponseWriter,r*http.Request){
ifsessions.IsLoggedIn(r){
ifr.Method=="POST"{
r.ParseForm()
text:=r.Form.Get("commentText")
id:=r.Form.Get("taskID")
idInt,err:=strconv.Atoi(id)
if(err!=nil)||(text==""){
log.Println("unabletoconvertintointeger")
message="Erroraddingcomment"
}else{
err=db.AddComments(idInt,text)
iferr!=nil{
log.Println("unabletoinsertintodb")
message="Commentnotadded"
}else{
message="Commentadded"
}
}
http.Redirect(w,r,"/",http.StatusFound)
}
}else{
http.Redirect(w,r,"/login",302)
}
}
Thebelowfilecontainsthecodetologinandlogoutofourapplication,webasicallyaregoingtosetthe"loggedin"propertyofourcookiestore.
Path:~/Tasks/Views/sessionViews.go
UserAuthentication
126
packageviews
import(
"net/http"
"github.com/thewhitetulip/Tasks/sessions"
)
//LogoutFuncImplementsthelogoutfunctionality.
//WIlldeletethesessioninformationfromthecookiestore
funcLogoutFunc(whttp.ResponseWriter,r*http.Request){
session,err:=sessions.Store.Get(r,"session")
iferr==nil{//Ifthereisnoerror,thenremovesession
ifsession.Values["loggedin"]!="false"{
session.Values["loggedin"]="false"
session.Save(r,w)
}
}
http.Redirect(w,r,"/login",302)
//redirecttologinirrespectiveoferrorornot
}
//LoginFuncimplementstheloginfunctionality,will
//addacookietothecookiestoreformanagingauthentication
funcLoginFunc(whttp.ResponseWriter,r*http.Request){
session,err:=sessions.Store.Get(r,"session")
iferr!=nil{
loginTemplate.Execute(w,nil)
//incaseoferrorduring
//fetchingsessioninfo,executelogintemplate
}else{
isLoggedIn:=session.Values["loggedin"]
ifisLoggedIn!="true"{
ifr.Method=="POST"{
ifr.FormValue("password")=="secret"
&&r.FormValue("username")=="user"{
session.Values["loggedin"]="true"
session.Save(r,w)
http.Redirect(w,r,"/",302)
return
}
}elseifr.Method=="GET"{
loginTemplate.Execute(w,nil)
}
}else{
http.Redirect(w,r,"/",302)
}
}
}
UserAuthentication
127
Thereisabetterwayofhandlingsessionsusingmiddleware,we'llintroducethatconceptinthenextchapter.
Users
Signingusersup
Theaboveexamplejusthardcodestheusernameandpassword,ofcoursewe'dwantpeopletosignuptoourservice.Wecreateausertable.
CREATETABLEuser(
idintegerprimarykeyautoincrement,
usernamevarchar(100),
passwordvarchar(1000),
emailvarchar(100)
);
Forthesakeofsimplicity,it'llonlycontainID,username,passwordandemail.
Therearetwopartshere,firstonewhereweallowuserstosignup,andanotherpartwherewereplacethehardcodedusernameandpasswordinourloginlogic.
file:~/Tasks/main.go
http.HandleFunc("/signup/",views.SignUpFunc)
file:~/Tasks/views/sessionViews.go
UserAuthentication
128
//SignUpFuncwillenablenewuserstosignuptoourservice
funcSignUpFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="POST"{
r.ParseForm()
username:=r.Form.Get("username")
password:=r.Form.Get("password")
email:=r.Form.Get("email")
log.Println(username,password,email)
err:=db.CreateUser(username,password,email)
iferr!=nil{
http.Error(w,"Unabletosignuserup",http.StatusInternalServerError)
}else{
http.Redirect(w,r,"/login/",302)
}
}
}
file:~/Tasks/db/user.go
//CreateUserwillcreateanewuser,takeasinputtheparametersand
//insertitintodatabase
funcCreateUser(username,password,emailstring)error{
err:=taskQuery("insertintouser(username,password,email)values(?,?,?)",user
name,password,email)
returnerr
}
WesawTaskQueryinourchapteronDB,itisasimplewrapperarounddb.Exec().
Note:Inarealwebapp,you'dwanttoencryptthepasswordandnotstoreitinplaintext,thisisadummyappwhich'llneverseethelightofthedaysoIamkeepingitplaintext.
Login
file~/Tasks/views/sessionViews.go
UserAuthentication
129
//LoginFuncimplementstheloginfunctionality,willaddacookietothecookiestore
formanagingauthentication
funcLoginFunc(whttp.ResponseWriter,r*http.Request){
session,err:=sessions.Store.Get(r,"session")
iferr!=nil{
log.Println("erroridentifyingsession")
loginTemplate.Execute(w,nil)
return
}
switchr.Method{
case"GET":
loginTemplate.Execute(w,nil)
case"POST":
log.Print("InsidePOST")
r.ParseForm()
username:=r.Form.Get("username")
password:=r.Form.Get("password")
if(username!=""&&password!="")&&db.ValidUser(username,password){
session.Values["loggedin"]="true"
session.Values["username"]=username
session.Save(r,w)
log.Print("user",username,"isauthenticated")
http.Redirect(w,r,"/",302)
return
}
log.Print("Invaliduser"+username)
loginTemplate.Execute(w,nil)
}
}
file~/Tasks/db/user.go
UserAuthentication
130
//ValidUserwillcheckiftheuserexistsindbandifexistsiftheusernamepassword
//combinationisvalid
funcValidUser(username,passwordstring)bool{
varpasswordFromDBstring
userSQL:="selectpasswordfromuserwhereusername=?"
log.Print("validatinguser",username)
rows:=database.query(userSQL,username)
deferrows.Close()
ifrows.Next(){
err:=rows.Scan(&passwordFromDB)
iferr!=nil{
returnfalse
}
}
//Ifthepasswordmatches,returntrue
ifpassword==passwordFromDB{
returntrue
}
//bydefaultreturnfalse
returnfalse
}
Note:
Sinceweareusinggorilla/sessions,wejusthavetopluginthefunctionalitythatthepackageprovidesanddon'thavetobotherabouttheactualimplementationofsessionshandling,ifyouareinterestedthenbyallmeansgoaheadandcheckoutthesourcecodeofgorilla/sessions!
Wekeepresettingthepasswordasanexercise,formulateamechanismtoresettingthepassword,requiresustocreateausertableandstoresomeprofileinformation,eitheremail-IDfromwherewe'llsendasecuritycodeorbydoingsomethingelse!Brainstorm!
UserAuthentication
131
FilesJSONandXMLaretwoofthemostcommonwaystotransmitdatabetweenwebapplications.We'lluseJSONforourconfigurationfile.
Forourwebapplicationwehaveasetofconfigurationvaluesliketheserverportwhereourapplicationwillrun.Supposeyouaredevelopingyourapplicationin$GOPATHandalsousingitsomewhereelse,thenyoucan'truntheminparallelbecausebothsourcesusethesameportnumber.Naturallywewantawaytoparameterizethatportnumber.Theparameterorconfigurationvaluelistmaycontainmorethingslikedatabaseconnectioninformation.Asofnowwewilluseaconfig.jsonfileandreadtheserverPortvariableandbindourserveronthatport.
Ourconfigurationfileusesafixedstructure,henceitissimpleenoughtoUnMarshalittoastructtype,wecanusesomeadvanceconceptstoaccomodateunstructuredJSONfiles,becausethatisthewholepointofJSON,wecanhavedatainanunstructuredformat.
NoSQLhasbeenfamouslately,theyarebasicallyJSONdocumentstores.Wehaveprojectslikeboltdbwhichstoredatainakeyvaluepair,ultimatelyinflatfilesorinmemory.
file:$GOPATH/src/github.com/thewhitetulip/Tasks/config/config.go
WorkingwithFiles
132
packageconfig
import(
"encoding/json"
"io/ioutil"
"log"
)
//Storesthemainconfigurationfortheapplication
//defineastructobjectcontaining
typeConfigurationstruct{
ServerPortstring
}
varerrerror
varconfigConfiguration
//ReadConfigwillreadtheconfig.jsonfiletoreadtheparameters
//whichwillbepassedintheconfigobject
funcReadConfig(fileNamestring)Configuration{
configFile,err:=ioutil.ReadFile(fileName)
iferr!=nil{
log.Fatal("Unabletoreadlogfile")
}
//log.Print(configFile)
err=json.Unmarshal(configFile,&config)
iferr!=nil{
log.Print(err)
}
returnconfig
}
file:$GOPATH/src/github.com/thewhitetulip/Tasks/config.json
{
"ServerPort":":8081"
}
file:$GOPATH/src/github.com/thewhitetulip/Tasks/main.go
values:=config.ReadConfig("config.json")
//valuesistheobjectnow,wecanusethe
//belowstatementtoaccesstheportname
values.ServerPort
WorkingwithFiles
133
Weusethejson.UnmarshaltoreadtheJSONfileintoourstructureobject.ThisisaverysimpleandbasicexampleofparsingJSONfiles,youcanhavenestedstructuresofmanylevelsinsidethemainconfigobject,butthatisthefeaturesofGo,solongasitcanberepresentedasaJSONdocumentyoucanusetheUnmarshalmethodtotranslatethefileintoanobjectwhichyoucanuseinyourprogram.
Homework
Altertheconfig.jsonfiletotakethenameofthesqlitedatabaseasaconfigurationparameter.ReadabouttheJSONlibraryingodoc
WorkingwithFiles
134
RoutingTillnowweusedroutingdirectlyinsideofourhandlers.Foralargeapplicationthough,it'dbebettertohavearouterinplace.Wecaneitheruseathirdpartyonewithcountlessfeaturesorthestandardmux.
Asourapplicationmatures,routingplaysabigroleintoit.Weintentionallyavoidedroutingtillnowbecauseasawebdeveloper,wemustunderstandwhathappensinthebackgroundofourapplication.Webframeworksallowustobuildapplicationsquickly,butthedownsideofthemisthattheyprovideuswithaframeworkasthenamesuggests,whichmeansyouaretotallyrestrictedbytheAPIwhichtheframeworkwillprovide.Thusweneedtoknowhowtoimplementbarebonestuff,soinfuturewemightwanttomodifytheframeworkweneed,orrathercreateourownone.
Firstofallwe'dneedtoinstallthehttprouter,doagoget-ugithub.com/julienschmidt/httprouter
Fromthedocumentation
PackagehttprouterisatriebasedhighperformanceHTTPrequestrouter.
Routing
135
packagemain
import(
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
)
funcIndex(whttp.ResponseWriter,r*http.Request,_httprouter.Params){
fmt.Fprint(w,"Welcome!\n")
}
funcHello(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){
fmt.Fprintf(w,"hello,%s!\n",ps.ByName("name"))
}
funcmain(){
router:=httprouter.New()//createsanewrouter
router.GET("/",Index)//willdirecttheGET/requesttotheIndexfunction
router.GET("/hello/:name",Hello)//willredirecttheGET/nametoHello,stores
thenameoftheparameter
//intheavariableofhttprouter.Params
log.Fatal(http.ListenAndServe(":8080",router))
}
httprouterusesacustomHttp.HandleFuncmethodtoaccomodateparameterizedrouting.
Here,wecanrouteourrequestsdependingontheHTTPmethodthroughwhichitisused,andwecanhandleparameterizedroutingforfree.
Tohandlethatscenariowewereinitiallyusingther.URL.Pathvariableandthenextractingtheparameters,itisjustthesolutionoffindingthevariableparameteroftheURL,thereisawideareawherehttprouterisawesome,thereisnoneedtohandlethe/foreachrequests.
Inwebapplicationssometimeshavingaforwardslashisverycritical,theURLindexistotallydifferentfrom/indexandismarginallydifferentfrom/index/httproutertakescareofthetrailingslashes.TheGodefaultMUXrequiresyoutohandletheroutesinthedescendingorder,meaningthemostgenericURLthe"/"shouldbeatthebottomandtheleastgenericshouldbeatthetop,asitgoessequentiallyupanddownmatchingtheURLs,httprouterprovidesawidevarietyofadvantageswhenitcomestorouting.
Thisisbecauseourapplicationshouldseparateroutingfromthehandlerlogic,weweremixingitjusttogetafeelforhowtoprograminGo,buteventuallywhentheappgrows,itistiresomeandnonmaintainablefordoingtheifr.Method==POSTcheckineachhandler,
Routing
136
ratherthanthatwecanhave3differenthandlersoneforwhenrequestcomesviaAJAX,onefornormalGETandonefornormalPOST.Thiswaywedonothaveonefunctionhandlercheckingthetypeofrequestandthenwritingthreeseparatelogicinsideonefunction.
Butthatdoesn'tmeanwehavetousehttprouter,ifinanapptherearen'tmuchsophisticatedroutingrequiredthenwecanusethedefaultMuxsinceitisgoodenough,butifwehavecomplicatedroutingthenhttprouteristhebest.
Homework
Readthehttprouterdocumentationandsourcecodetogetadeeperunderstandingofrouters,sinceroutersareanintegralpartofanywebapplication.Thenrewriteourapplicationusingthehttprouter.
Routing
137
MiddlewaresMiddlewareisreallyanythingthatextendsyourwebappinamodularway.Mostcommonexamplesareprobablyparsingtherequestparameters/bodyandstoringtheminaneasily-accessibleformatsoyoudon'thavetodoitineverysinglehandler,orsessionhandlingaswementionedinthepreviouschapter.OtherexamplescouldbethrottlingorIPfiltering,whichwouldalsohappenbeforeyoustartbuildingyourresponse,orcompression,whichwouldhappenafteryou'vebuiltyourresponse.
//RequiresLoginisamiddlewarewhichwillbeusedforeach
//httpHandlertocheckifthereisanyactivesession
funcRequiresLogin(handlerfunc(whttp.ResponseWriter,r*http.Request))
func(whttp.ResponseWriter,r*http.Request){
returnfunc(whttp.ResponseWriter,r*http.Request){
if!sessions.IsLoggedIn(r){
http.Redirect(w,r,"/login/",302)
return
}
handler(w,r)
}
}
Theabovefunctioncountsasmiddleware-itdoesn'tknowanythingaboutyourappexcepthowyouhandlesessions.Ifyou'renotloggedin,itredirects,otherwiseitdoesn'tdoanythingandpassesalongtothenexthandler.Thatnexthandlermightbewhereyouactuallybuildyourresponse,oritcouldbeanothermiddlewarecomponentthatdoessomethingelsefirst.
Toknowsomeone'sloggedin,yes,youwanttocreateasessionidentifierandstorethatsomewhereontheserverside(inmemoryoradatabase)andalsosetitintheuser'scookies.YoursessionIDsshouldbesufficientlyrandomandlongthattheycouldn'tbeeasilyguessed.IthinkacommonwayofsatisfyingthatiscreatingaUUIDandthenbase64encodethat.Or,youcouldjustgenerateabunchofrandombytes.
Toknowwhichuserisloggedin,thesessionIDshouldbethekeythatmapstoauserID.So,you'dmakeamapofSessionID=>UserID,orsomethingsimilarinyourdatabase.
Then,beforeeveryrequest,you'd
1. Checkuser'scookiesforSessionID.Ifnone,userisnotloggedin.2. Checkyourstoreforuser'sSessionID.Ifit'snotfound,thenit'sinvalid-userisnot
loggedin.3. Ifyoufoundit,useittolookuptheuser'sID.Userisnowloggedin.
Middleware
138
4. Nowyou'vegottheuserIDandcanuseitasafilterwhenqueryingyourDBifyouonlywanttoshowthatuser'stasks.
ExampleWithoutmiddleware:
//IsLoggedInwillcheckiftheuserhasanactivesessionandreturnTrue
funcIsLoggedIn(r*http.Request)bool{
session,_:=Store.Get(r,"session")
ifsession.Values["loggedin"]=="true"{
returntrue
}
returnfalse
}
//SearchTaskFuncisusedtohandlethe/search/url,handlesthesearchfunction
funcSearchTaskFunc(whttp.ResponseWriter,r*http.Request){
ifsessions.IsLoggedin(r){
ifr.Method=="POST"{
r.ParseForm()
query:=r.Form.Get("query")
context:=db.SearchTask(query)
categories:=db.GetCategories()
context.Categories=categories
searchTemplate.Execute(w,context)
}
}else{
http.Redirect(w,r,"/login/",302)
}
WithMiddleware:
http.HandleFunc("/",views.RequiresLogin(views.ShowAllTasksFunc))
//SearchTaskFuncisusedtohandlethe/search/url,
//handlesthesearchfunction
funcSearchTaskFunc(whttp.ResponseWriter,r*http.Request){
ifr.Method=="POST"{
r.ParseForm()
query:=r.Form.Get("query")
context:=db.SearchTask(query)
categories:=db.GetCategories()
context.Categories=categories
searchTemplate.Execute(w,context)
}
}
Middleware
139
Thisway,wedonothavetorepeattheifsessions.IsLoggedin()blockineachofourviewwhichrequiresauthentication.Inthisexamplewehaveuseditforsessionhandling,butitcanbeusedforanypurposewhichrequiressomekindofprehandlingofanyview.
Middleware
140
BuildinganAPIAPIstandsforApplicationProgrammingInterface,itisjustaninterfacetothewebapp.Whenweuseabrowsertoaccessawebapplication,weinteractinHTTPandgetbackHTMLpages,whichthebrowserwillrenderforus.Let'ssaywewanttointeractwithourwebapptogetsomedataoutofitusingahostprogramminglanguagelikeGoorPython.We'dhavetomaintaincookiesinPython,orwriteapythonmoduleasanextensionofabrowsertohandlethisscenario.
Thereisasimplewayoutofthis,weequipourwebapplicationitselftointeractwithanyhostlanguagewhichcantalkinHTTP.Thiswaydeveloperscaneasilygetaccesstooursystem,usingvalidcredentials,ofcourse.
Browser:
1. Wesendtheusername,passwordandgetacookiestoredonourmachine.2. WeusethetokeninthecookieuntilitisvalidtosendHTTPrequests.3. ThebrowserisresponsibleforrenderingtheHTMLpagessentbytheserver.
API:
1. Wesendtheusername,passwordandgetatoken.2. Wesendthistokenineachofourrequesttotheserver
TypicallywesendthetokeninacustomHTTPheadercalledtoken.
Whenweuseabrowser,theserverstoresourinformationasasession,whenwesenditarequest,itisawareofoursession.AwebapptypicallyusescookiestostorethesessionID,whichisusedtoidentifytheuser.Suchaserveriscalledastatefulserver.
WhenwewriteAPIs,theyarestatelessservers,theydonotstoresessionsinformationanywhereontheserver.Toit,eachrequestisunique.Whichiswhy,weneedtopassalongtheauthenticationtokenineachrequest.
Note:Don'tmessaroundwithtokens
Thereareappswhere"singlesignin"featureisavailable,theuserhastologinonlyonceandtheyareloggedinforever,thisisverydangerous.Becauseifamaliciouspersongetstheirhandsonthesecuritytoken,theycansendmaliciousrequestsfordatawhichlookgenuineandareimpossibletoclasifyasmalicious.Don'tdothis,alwayshavesomeexpirationtimeforsecuritytokens,dependsonyourapplicationreally,twohours,sixhours,butneverinfinitehours.
BuildinganAPI
141
JWTJavascriptWebTokensisastandardforgeneratingtokens.Wewillusethejwt-golibrary.
Letsstartbydefiningourroutes
http.HandleFunc("/api/get-task/",views.GetTasksFuncAPI)
http.HandleFunc("/api/get-deleted-task/",views.GetDeletedTaskFuncAPI)
http.HandleFunc("/api/add-task/",views.AddTaskFuncAPI)
http.HandleFunc("/api/update-task/",views.UpdateTaskFuncAPI)
http.HandleFunc("/api/delete-task/",views.DeleteTaskFuncAPI)
http.HandleFunc("/api/get-token/",views.GetTokenHandler)
http.HandleFunc("/api/get-category/",views.GetCategoryFuncAPI)
http.HandleFunc("/api/add-category/",views.AddCategoryFuncAPI)
http.HandleFunc("/api/update-category/",views.UpdateCategoryFuncAPI)
http.HandleFunc("/api/delete-category/",views.DeleteCategoryFuncAPI)
file:main.go
WewillhavethesameURLsfortheAPI,butit'llstartwith/api/
OurlogicisthatwewillsendtheusernameandpasswordinaPOSTrequestto/api/get-token/thatwillreturnthetokenforus.
file:views/api.go
BuildinganAPI
142
import"github.com/dgrijalva/jwt-go"
varmySigningKey=[]byte("secret")
//GetTokenHandlerwillgetatokenfortheusernameandpassword
funcGetTokenHandler(whttp.ResponseWriter,r*http.Request){
ifr.Method=="POST"{
//specifythealgorithmtogeneratetoken
token:=jwt.New(jwt.SigningMethodHS256)
r.ParseForm()
username:=r.Form.Get("username")
password:=r.Form.Get("password")
ifdb.ValidUser(username,password){
/*Settokenclaimsliketheusernamd
andtheexpirationtime*/
token.Claims["username"]=username
token.Claims["exp"]=time.Now().Add(time.Hour*2).Unix()
/*Signthetokenwithoursecretwhichisaglobal
variableinthesamefile*/
tokenString,_:=token.SignedString(mySigningKey)
/*Finally,writethetokentothebrowserwindow*/
w.Write([]byte(tokenString))
}else{
w.Write([]byte("Authenticationfailed"))
}
}
}
Thenextstepistovalidateatoken.
//ValidateTokenwillvalidatethetoken
funcValidateToken(myTokenstring)(bool,string){
token,err:=jwt.Parse(myToken,func(token*jwt.Token)(interface{},error){
return[]byte(mySigningKey),nil
})
if(err!=nil)||(!token.Valid){
returnfalse,""
}
returntoken.Valid,token.Claims["username"].(string)
}
BuildinganAPI
143
We'llcalltheParsemethodonthetokenwhichwereceiveasaparameterinthefunctioncall.Thetoken.Validfieldisaboolenvariablewhichistrueifthetokenisvalidandfalseotherwise.
MakinganAPIcallMakinganAPIcallisanalogoustoournormalview.
BuildinganAPI
144
//GetCategoryFuncAPIwillreturnthecategoriesfortheuser
//dependsontheIDthatweget,ifwegetall,thenreturnall
//categoriesoftheuser
funcGetCategoryFuncAPI(whttp.ResponseWriter,r*http.Request){
ifr.Method=="GET"{
varerrerror
varmessagestring
varstatustypes.Status
//getthecustomHTTPheadercalledToken
token:=r.Header["Token"][0]
w.Header().Set("Content-Type","application/json;charset=UTF-8")
IsTokenValid,username:=ValidateToken(token)
//Whenthetokenisnotvalidshowthe
//defaulterrorJSONdocument
if!IsTokenValid{
status=types.Status
{
StatusCode:http.StatusInternalServerError,
Message:message
}
w.WriteHeader(http.StatusInternalServerError)
//thefollowingstatementwillwritetheJSONdocumentto
//theHTTPResponseWriterobject.
err=json.NewEncoder(w).Encode(status)
iferr!=nil{
panic(err)
}
return
}
log.Println("tokenisvalid"+username+"isloggedin")
categories:=db.GetCategories(username)
w.Header().Set("Content-Type","application/json;charset=UTF-8")
w.WriteHeader(http.StatusOK)
err=json.NewEncoder(w).Encode(categories)
iferr!=nil{
panic(err)
}
}
}
DuringanAPIcall,wesenddatainJSONformat,forthat,weneedtosetourcontent-typeasapplication/json,bydoingthis,evenawebbrowserwilldetectthatitisgettingaJSONdocument.WhenweneedtowriteaJSONdocumenttotheresponsewriterobject,weuse
BuildinganAPI
145
thejson.NewEncoder(w).Encode(categories)method,wherecategoriesisourJSONdocument.
FormattingaJSONdocumentThis,below,isourTasksstruct,whichwillbepopulatedasaJSONdocumentwhenwerunourserver.Asyoumightknow,wecan'tuseCapitalletterasthefirstletterinaJSONtitle,byconventiontheyshouldallbesmallletters.Gohasaspecialwayoflettingusdothat.Theexampleisbelow,whenwewritejson:"id",wearetellingGothatthenameofthisfieldinaJSONrenderingshouldbeidandnotId.Thereisanotherspecialsyntaxcalledomitempty,insomeJSOndocumentsyoumightwantsomefieldtonotbedisplayed.Itsohappensthattherearefieldswhichyouwouldwanttodisappearwhentheirvaluesaren'tpresent,theymaynotbeimportantorit'dbetooclunkytohavethemasNULLinallJSONdocuments.
typeTaskstruct{
Idint`json:"id"`
Titlestring`json:"title"`
Contentstring`json:"content"`
Createdstring`json:"created"`
Prioritystring`json:"priority"`
Categorystring`json:"category"`
Refererstring`json:"referer,omitempty"`
Comments[]Comment`json:"comments,omitempty"`
IsOverduebool`json:"isoverdue,omitempty"`
}
TestingAPIWe'lluseFirefoxandRestClientextensiontotestourAPI.RestClientallowsustosendvariousrequeststoourAPIserver,ifyouareonChrome,POSTmanisthebestalternative.
ForRestClienttosendFormdata,setacustomheaderName:Content-TypeValue:application/x-www-form-urlencoded
Otherwiseyou'llbesendingblankPOSTrequestsallthetime.Theserverneedstounderstandthecontenttypeofthedataitisgettingformtheclient.
Tosendtheactualformdata,example:wehavethreefields,username,passwordandname.Thenwewriteitinthebodysectionlikethis:
username=thewhitetulip&password=password&name=thewhitetulip
BuildinganAPI
146
AlsosetacustomHTTPheaderbytheName:tokenValue:thetokenwhichyougetin/api/get-token/call
BuildinganAPI
147
1.
Contributors
148