Table of Contents...Go basic knowledge Go foundation Control statements and functions struct...
Transcript of Table of Contents...Go basic knowledge Go foundation Control statements and functions struct...
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