Table of Contents...Go basic knowledge Go foundation Control statements and functions struct...

Preview:

Citation preview

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

Recommended