рдЧреЛ рдкрд░ рдПрдХ рд╡реЗрдм рд╕реЗрд╡рд╛ рд▓рд┐рдЦрдирд╛ (рднрд╛рдЧ рджреЛ)

рдЧреЛ рдкрд░ рдПрдХ рдЫреЛрдЯрд╛, рдкреВрд░реНрдг рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рд╡рд╛рд▓рд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓рд┐рдЦрдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд▓реЗрдЦ рдХреА рдирд┐рд░рдВрддрд░рддрд╛ред

рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ, рд╣рдордиреЗ REST API рдХрд╛рд░реНрдпрд╛рдиреНрд╡рд┐рдд рдХрд┐рдпрд╛ рдФрд░ рдЖрдиреЗ рд╡рд╛рд▓реЗ HTTP рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рдПрдХрддреНрд░ рдХрд░рдирд╛ рд╕реАрдЦрд╛ред рдЗрд╕ рднрд╛рдЧ рдореЗрдВ, рд╣рдо рдЕрдкрдиреЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╡рд░ рдХрд░реЗрдВрдЧреЗ, AngularJS рдФрд░ рдмреВрдЯрд╕реНрдЯреНрд░реИрдк рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдПрдХ рд╕реБрдВрджрд░ рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдЬреЛрдбрд╝реЗрдВрдЧреЗ, рдФрд░ рд╡рд┐рднрд┐рдиреНрди рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдПрдХреНрд╕реЗрд╕ рдкреНрд░рддрд┐рдмрдВрдз рд▓рд╛рдЧреВ рдХрд░реЗрдВрдЧреЗред


рдЗрд╕ рднрд╛рдЧ рдореЗрдВ, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЪрд░рдг рд╣рдореЗрдВ рдЗрдВрддрдЬрд╛рд░ рдХрд░ рд░рд╣реЗ рд╣реИрдВ:
  1. рдЪреМрдерд╛ рдЪрд░рдгред рд▓реЗрдХрд┐рди рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХрд╛ рдХреНрдпрд╛?
  2. рдкрд╛рдВрдЪ рдЪрд░рдг - рдЕрд▓рдВрдХрд░рдг рдФрд░ рдПрдХ рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕;
  3. рдЪрд░рдг рдЫрд╣ рдХреБрдЫ рдЧреЛрдкрдиреАрдпрддрд╛ рдЬреЛрдбрд╝реЗрдВред
  4. рд╕рд╛рддрд╡рд╛рдВ рдЪрд░рдгред рд╣рдо рдЕрдирд╛рд╡рд╢реНрдпрдХ рдХреЛ рд╕рд╛рдл рдХрд░рддреЗ рд╣реИрдВ;
  5. рдЖрда рдХрджрдоред рд╣рдо рднрдВрдбрд╛рд░рдг рдХреЗ рд▓рд┐рдП рд░реЗрдбрд┐рд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред


рдЪреМрдерд╛ рдЪрд░рдгред рд▓реЗрдХрд┐рди рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХрд╛ рдХреНрдпрд╛?


рдХрд┐рд╕реА рднреА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдкрд░реАрдХреНрд╖рдгреЛрдВ рд╕реЗ рдврдВрдХрдирд╛ рдЪрд╛рд╣рд┐рдП, рдЪрд╛рд╣реЗ рд╡рд╣ рдХрд┐рддрдирд╛ рднреА рдмрдбрд╝рд╛ рдХреНрдпреЛрдВ рди рд╣реЛред рдЧреЛ рдХреЗ рдкрд╛рд╕ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдбрд╝реА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рдЙрдкрдХрд░рдг рд╣реИрдВред рд╕рд╛рдзрд╛рд░рдг рдпреВрдирд┐рдЯ рдЯреЗрд╕реНрдЯ (рдпреВрдирд┐рдЯ рдЯреЗрд╕реНрдЯ) рджреЛрдиреЛрдВ рдХреЛ рд▓рд┐рдЦрдирд╛ рд╕рдВрднрд╡ рд╣реИ, рдФрд░, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдмреЗрдВрдЪрдорд╛рд░реНрдХ рдЯреЗрд╕реНрдЯред рдЯреВрд▓рдХрд┐рдЯ рдЖрдкрдХреЛ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╕рд╛рде рдХреЛрдб рдХрд╡рд░реЗрдЬ рджреЗрдЦрдиреЗ рдХреА рднреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рдкрд░реАрдХреНрд╖рдг рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореВрд▓ рдкреИрдХреЗрдЬ рдкрд░реАрдХреНрд╖рдг рд╣реИред рдпрд╣рд╛рдВ рджреЛ рдореБрдЦреНрдп рдкреНрд░рдХрд╛рд░ рд╕рд╛рдорд╛рдиреНрдп рдпреВрдирд┐рдЯ рдЯреЗрд╕реНрдЯ рдХреЗ рд▓рд┐рдП T рдФрд░ рд▓реЛрдб рдЯреЗрд╕реНрдЯ рдХреЗ рд▓рд┐рдП B ред рдЧреЛ рдореЗрдВ рдЯреЗрд╕реНрдЯ рдореБрдЦреНрдп рдкреИрдХреЗрдЬ рдХреЗ рд░реВрдк рдореЗрдВ рдПрдХ рд╣реА рдкреИрдХреЗрдЬ рдореЗрдВ рд▓рд┐рдЦреЗ рдЧрдП рд╣реИрдВ, рдЬрд┐рд╕рдореЗрдВ рдкреНрд░рддреНрдпрдп _test рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, рдкреИрдХреЗрдЬ рдХреЗ рдЕрдВрджрд░ рдЙрдкрд▓рдмреНрдз рдХреЛрдИ рднреА рдирд┐рдЬреА рдбреЗрдЯрд╛ рд╕рдВрд░рдЪрдирд╛рдПрдВ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рдЕрдВрджрд░ рднреА рдЙрдкрд▓рдмреНрдз рд╣реИрдВ (рдпрд╣ рднреА рд╕рдЪ рд╣реИ рдХрд┐ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдореЗрдВ рдПрдХ рджреВрд╕рд░реЗ рдХреЗ рд╕рд╛рде рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рд╡реИрд╢реНрд╡рд┐рдХ рдЧреБрдВрдЬрд╛рдЗрд╢ рд╣реИ)ред рдореБрдЦреНрдп рдХрд╛рд░реНрдпрдХреНрд░рдо рдХрд╛ рд╕рдВрдХрд▓рди рдХрд░рддреЗ рд╕рдордп, рдкрд░реАрдХреНрд╖рдг рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдЕрдирджреЗрдЦрд╛ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рдмреБрдирд┐рдпрд╛рджреА рдкрд░реАрдХреНрд╖рдг рдкреИрдХреЗрдЬ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдмрдбрд╝реА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рддреАрд╕рд░реЗ рдкрдХреНрд╖ рдХреЗ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╣реИрдВ рдЬреЛ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд▓реЗрдЦрди рдХреЛ рд╕рд░рд▓ рдмрдирд╛рдиреЗ рдореЗрдВ рдорджрдж рдХрд░рддреЗ рд╣реИрдВ рдпрд╛ рдПрдХ рдпрд╛ рдХрд┐рд╕реА рдЕрдиреНрдп рд╢реИрд▓реА (рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдмреАрдбреАрдбреА рд╢реИрд▓реА рдореЗрдВ) рдореЗрдВ рд▓реЗрдЦрди рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВред рдпрд╣рд╛рдБ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЯреАрдбреАрдбреА рд╢реИрд▓реА рдореЗрдВ рдЧреЛ рдХреЛ рдХреИрд╕реЗ рд▓рд┐рдЦреЗрдВ, рдЗрд╕ рдкрд░ рдПрдХ рдЕрдЪреНрдЫрд╛ рдкрд░рд┐рдЪрдпрд╛рддреНрдордХ рд▓реЗрдЦ рд╣реИред

GitHub рдкрд░ рдкрд░реАрдХреНрд╖рдг рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреА рддреБрд▓рдирд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреНрд▓реЗрдЯ рд╣реИ, рдЬрд┐рд╕рдХреЗ рдмреАрдЪ рдореЗрдВ goconvey рдЬреИрд╕реЗ рд░рд╛рдХреНрд╖рд╕ рд╣реИрдВ, рдЬреЛ рдПрдХ рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рднреА рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ, рдФрд░ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХрд░рддрд╛ рд╣реИ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдкрд╛рд╕рд┐рдВрдЧ рдЯреЗрд╕реНрдЯ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реВрдЪрдирд╛рдПрдВред рд▓реЗрдХрд┐рди, рдЪреАрдЬреЛрдВ рдХреЛ рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рд╣рдо рдПрдХ рдЫреЛрдЯреА рд╕реА рдЧрд╡рд╛рд╣реА рджреЗрдиреЗ рд╡рд╛рд▓реА рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд▓реЗрдВрдЧреЗ рдЬреЛ рд╕реНрдерд┐рддрд┐рдпреЛрдВ рдХреА рдЬрд╛рдБрдЪ рдХрд░рдиреЗ рдФрд░ рдирдХрд▓реА рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдХреБрдЫ рдЖрджрд┐рдо рдЬреЛрдбрд╝рддрд╛ рд╣реИред

рдЪреМрдереЗ рдЪрд░рдг рдХреЗ рд▓рд┐рдП рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ:

 git checkout step-4 

рдЖрдЗрдП рдореЙрдбрд▓реЛрдВ рдХреЗ рд▓рд┐рдП рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрдХрд░ рд╢реБрд░реВ рдХрд░реЗрдВред рдлрд╝рд╛рдЗрд▓ рдореЙрдбрд▓ рдмрдирд╛рдПрдВ_test.goред рдЧреЛ рдкрд░реАрдХреНрд╖рдг рдЙрдкрдпреЛрдЧрд┐рддрд╛ рджреНрд╡рд╛рд░рд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рд░реНрдпреЛрдВ рдХреЛ рдирд┐рдореНрди рдкреИрдЯрд░реНрди рдХреЛ рдкреВрд░рд╛ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП:

 func TestXxx(*testing.T) 

рд╣рдо рдЕрдкрдирд╛ рдкрд╣рд▓рд╛ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦреЗрдВрдЧреЗ, рдЬреЛ рдмрд┐рди рд╡рд╕реНрддреБ рдХреЗ рд╕рд╣реА рдирд┐рд░реНрдорд╛рдг рдХреА рдЬрд╛рдБрдЪ рдХрд░реЗрдЧрд╛:

 func TestNewBin(t *testing.T) { now := time.Now().Unix() bin := NewBin() if assert.NotNil(t, bin) { assert.Equal(t, len(bin.Name), 6) assert.Equal(t, bin.RequestCount, 0) assert.Equal(t, bin.Created, bin.Updated) assert.True(t, bin.Created < (now+1)) assert.True(t, bin.Created > (now-1)) } } 

рдЧрд╡рд╛рд╣реА рдореЗрдВ рд╕рднреА рдкрд░реАрдХреНрд╖рдг рд╡рд┐рдзрд┐рдпрд╛рдБ * рдкрд░реАрдХреНрд╖рдг рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддреА рд╣реИрдВред рдкрд╣рд▓рд╛ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд░реВрдк рдореЗрдВ рдСрдмреНрдЬреЗрдХреНрдЯред
рдЕрдЧрд▓рд╛, рд╣рдо рд╕рднреА рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рддреЗ рд╣реИрдВ, рдЧрд▓рдд рд░рд╛рд╕реНрддреЛрдВ рдФрд░ рд╕реАрдорд╛ рдореВрд▓реНрдпреЛрдВ рдХреЛ рдирд╣реАрдВ рднреВрд▓рддреЗ рд╣реИрдВред рдореИрдВ рд▓реЗрдЦ рдХреЗ рд╕рднреА рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рдХреЛрдб рдХрд╛ рд╣рд╡рд╛рд▓рд╛ рдирд╣реАрдВ рджреВрдВрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рдЙрдирдореЗрдВ рд╕реЗ рдмрд╣реБрдд рд╕рд╛рд░реЗ рд╣реИрдВ, рдФрд░ рдЖрдк рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдЙрдирдХреЗ рд╕рд╛рде рдЦреБрдж рдХреЛ рдкрд░рд┐рдЪрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдореИрдВ рдХреЗрд╡рд▓ рд╕рдмрд╕реЗ рджрд┐рд▓рдЪрд╕реНрдк рдмрд┐рдВрджреБрдУрдВ рдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд░реВрдВрдЧрд╛ред

Api_test.go рдлрд╝рд╛рдЗрд▓ рдкрд░ рдзреНрдпрд╛рди рджреЗрдВ, рдЗрд╕рдореЗрдВ рд╣рдо рдЕрдкрдиреЗ REST API рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рддреЗ рд╣реИрдВред рд╣рдорд╛рд░реЗ рдбреЗрдЯрд╛ рдХреЗ рднрдВрдбрд╛рд░рдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдкрд░ рдирд┐рд░реНрднрд░ рдирд╣реАрдВ рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рдирдХрд▓реА рд╡рд╕реНрддреБ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ рдЬреЛ рд╕реНрдЯреЛрд░реЗрдЬ рдЗрдВрдЯрд░рдлреЗрд╕ рдХреЗ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддреА рд╣реИред рд╣рдо рдирдХрд▓реА рдкреИрдХреЗрдЬ рдХреА рдЧрд╡рд╛рд╣реА рджреЗрддреЗ рд╣реБрдП рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред рдпрд╣ рдЖрд╕рд╛рдиреА рд╕реЗ рдирдХрд▓реА рд╡рд╕реНрддреБрдУрдВ рдХреЛ рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рддрдВрддреНрд░ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрдиреЗ рдХреЗ рджреМрд░рд╛рди рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╡рд╕реНрддреБрдУрдВ рдХреЗ рдмрдЬрд╛рдп рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

рдпрд╣рд╛рдБ рдЙрд╕рдХрд╛ рдХреЛрдб рд╣реИ:

 type MockedStorage struct{ mock.Mock } func (s *MockedStorage) CreateBin(_ *Bin) error { args := s.Mock.Called() return args.Error(0) } func (s *MockedStorage) UpdateBin(bin *Bin) error { args := s.Mock.Called(bin) return args.Error(0) } func (s *MockedStorage) LookupBin(name string) (*Bin, error) { args := s.Mock.Called(name) return args.Get(0).(*Bin), args.Error(1) } func (s *MockedStorage) LookupBins(names []string) ([]*Bin, error) { args := s.Mock.Called(names) return args.Get(0).([]*Bin), args.Error(1) } func (s *MockedStorage) LookupRequest(binName, id string) (*Request, error) { args := s.Mock.Called(binName, id) return args.Get(0).(*Request), args.Error(1) } func (s *MockedStorage) CreateRequest(bin *Bin, req *Request) error { args := s.Mock.Called(bin) return args.Error(0) } func (s *MockedStorage) LookupRequests(binName string, from, to int) ([]*Request, error) { args := s.Mock.Called(binName, from, to) return args.Get(0).([]*Request), args.Error(1) } 

рдПрдкреАрдЖрдИ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░рддреЗ рд╕рдордп рдкрд░реАрдХреНрд╖рдгреЛрдВ рдореЗрдВ рдЖрдЧреЗ, рд╣рдо рдЕрдкрдиреА рдирдХрд▓реА рд╡рд╕реНрддреБ рдХреЛ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░рддреЗ рд╣реИрдВ:

  req, _ := http.NewRequest("GET", "/api/v1/bins/", nil) api = GetApi() mockedStorage := &MockedStorage{} api.MapTo(mockedStorage, (*Storage)(nil)) res = httptest.NewRecorder() mockedStorage.On("LookupBins", []string{}).Return([]*Bin(nil), errors.New("Storage error")) api.ServeHTTP(res, req) mockedStorage.AssertExpectations(t) if assert.Equal(t, res.Code, 500) { assert.Contains(t, res.Body.String(), "Storage error") } 

рдкрд░реАрдХреНрд╖рдг рдореЗрдВ, рд╣рдо рдореЙрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рд▓рд┐рдП рдЕрдкреЗрдХреНрд╖рд┐рдд рдЕрдиреБрд░реЛрдзреЛрдВ рдФрд░ рдЙрдирдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЙрддреНрддрд░реЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕рд▓рд┐рдП, рдЬрд┐рд╕ рд╕рдордп рд╣рдо s.Mock.Called(names) рд╡рд┐рдзрд┐ рдХреЛ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреА рдирдХрд▓реА рд╡рд┐рдзрд┐ рдХреЗ рдЕрдВрджрд░ рдХрд╣рддреЗ рд╣реИрдВ, рдпрд╣ рджрд┐рдП рдЧрдП рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рдкрддреНрд░рд╛рдЪрд╛рд░ рдФрд░ рд╡рд┐рдзрд┐ рдХреЗ рдирд╛рдо рдХреЛ рдЦреЛрдЬрдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдЬрдм рд╣рдо args.Get (0) рд╡рд╛рдкрд╕ рдЖрддреЗ рд╣реИрдВ, рддреЛ рдкрд╣рд▓рд╛ рддрд░реНрдХ рд░рд┐рдЯрд░реНрди рдореЗрдВ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред , рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ realBinред рдЧреЗрдЯ рдореЗрдердб рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЬреЛ рдЯрд╛рдЗрдк рдЗрдВрдЯрд░рдлреЗрд╕ {} рдХрд╛ рдСрдмреНрдЬреЗрдХреНрдЯ рд▓реМрдЯрд╛рддрд╛ рд╣реИ, рдЗрд╕рдореЗрдВ рд╣реЗрд▓реНрдкрд░ рдореЗрдердбреНрд╕, рд╕реНрдЯреНрд░рд┐рдВрдЧ, рдмреВрд▓, рдПрд░рд░ рд╣реЛрддреЗ рд╣реИрдВ, рдЬреЛ рдЗрдВрдЯрд░рдлреЗрд╕ рдХреЛ рд╣рдорд╛рд░реА рдЬрд░реВрд░рдд рдХреЗ рдкреНрд░рдХрд╛рд░ рдореЗрдВ рдмрджрд▓ рджреЗрддреЗ рд╣реИрдВред рдкрд░реАрдХреНрд╖рдг рдХреЗ рджреМрд░рд╛рди рд╣рдорд╛рд░реЗ рджреНрд╡рд╛рд░рд╛ рд╕рднреА рдЕрдкреЗрдХреНрд╖рд┐рдд рддрд░реАрдХреЛрдВ рдХреЛ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП mockedStorage.AssertExpectations (t) рд╡рд┐рдзрд┐ рдЬрд╛рдБрдЪ рдХрд░рддрд╛ рд╣реИред

RespptRecorder рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЛ canptest.NewRecorder рдореЗрдВ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдпрд╣рд╛рдБ рднреА рджрд┐рд▓рдЪрд╕реНрдк рд╣реИ, рдпрд╣ ResponseWriter рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╣рдореЗрдВ рдЕрдиреБрд░реЛрдз рдбреЗрдЯрд╛ рдХреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд┐рдП рдмрд┐рдирд╛ рдХрд╣реАрдВ рднреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдЖрдЦрд┐рд░рдХрд╛рд░ рдХреНрдпрд╛ рд╣реЛрдЧрд╛ (рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЛрдб, рд╣реЗрдбрд░ рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдирд┐рдХрд╛рдп)ред

рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рдХрдорд╛рдВрдб рдЪрд▓рд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:

 > go test ./src/skimmer ok _/.../src/skimmer 0.032s 

рдкрд░реАрдХреНрд╖рдг рд▓реЙрдиреНрдЪ рдЯреАрдо рдореЗрдВ рдмрдбрд╝реА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рдЭрдВрдбреЗ рд╣реИрдВ, рдЖрдк рдЦреБрдж рдХреЛ рдЙрдирдХреЗ рд╕рд╛рде рдЗрд╕ рддрд░рд╣ рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

 > go help testflag 

рдЖрдк рдЙрдирдХреЗ рд╕рд╛рде рдЦреЗрд▓ рд╕рдХрддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдЕрдм рд╣рдо рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХрдорд╛рдВрдб рдореЗрдВ рд░реБрдЪрд┐ рд░рдЦрддреЗ рд╣реИрдВ (рдЧреЛ рд╕рдВрд╕реНрдХрд░рдг 1.2 рдХреЗ рд▓рд┐рдП рдкреНрд░рд╛рд╕рдВрдЧрд┐рдХ):

 > go test ./src/skimmer/ -coverprofile=c.out && go tool cover -html=c.out 

рдпрджрд┐ рдпрд╣ рдЖрдкрдХреЗ рд▓рд┐рдП рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рддреЛ рдЖрдкрдХреЛ рдкрд╣рд▓реЗ рдХрд╡рд░реЗрдЬ рдЯреВрд▓ рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдирд╛ рдкрдбрд╝ рд╕рдХрддрд╛ рд╣реИ

 > go get code.google.com/p/go.tools/cmd/cover 

рдпрд╣ рдХрдорд╛рдВрдб рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рддрд╛ рд╣реИ рдФрд░ c.out рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рдЯреЗрд╕реНрдЯ рдХрд╡рд░реЗрдЬ рдкреНрд░реЛрдлрд╛рдЗрд▓ рдХреЛ рдмрдЪрд╛рддрд╛ рд╣реИ, рдФрд░ рдлрд┐рд░ go tool html рд╕рдВрд╕реНрдХрд░рдг рдмрдирд╛рддрд╛ рд╣реИ, рдЬреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЦреБрд▓рддрд╛ рд╣реИред
рдЧреЛ рдореЗрдВ рдЯреЗрд╕реНрдЯ рдХрд╡рд░реЗрдЬ, рдХрд╛рдлреА рджрд┐рд▓рдЪрд╕реНрдк рддрд░реАрдХреЗ рд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЧрдпрд╛ред рдХреЛрдб рд╕рдВрдХрд▓рд┐рдд рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ, рд╕реНрд░реЛрдд рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдмрджрд▓ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдХрд╛рдЙрдВрдЯрд░реЛрдВ рдХреЛ рд╕реНрд░реЛрдд рдХреЛрдб рдореЗрдВ рдбрд╛рд▓рд╛ рдЬрд╛рддрд╛ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдРрд╕рд╛ рдХреЛрдб:

 func Size(a int) string { switch { case a < 0: return "negative" case a == 0: return "zero" } return "enormous" } 

рдЗрд╕ рдореЗрдВ рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИ:

 func Size(a int) string { GoCover.Count[0] = 1 switch { case a < 0: GoCover.Count[2] = 1 return "negative" case a == 0: GoCover.Count[3] = 1 return "zero" } GoCover.Count[1] = 1 return "enormous" } 

рдпрд╣ рди рдХреЗрд╡рд▓ рдХрд╡рд░реЗрдЬ рджрд┐рдЦрд╛рдирд╛ рд╕рдВрднрд╡ рд╣реИ, рдмрд▓реНрдХрд┐ рдХреЛрдб рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рдЕрдиреБрднрд╛рдЧ рдХреЛ рдХрд┐рддрдиреА рдмрд╛рд░ рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд╣рдореЗрд╢рд╛ рдХреА рддрд░рд╣, рдЖрдк рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдЕрдзрд┐рдХ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВред

рдЕрдм рдЬрдм рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдкреВрд░реНрдг REST API рд╣реИ, рдФрд░ рдкрд░реАрдХреНрд╖рдгреЛрдВ рд╕реЗ рдЖрдЪреНрдЫрд╛рджрд┐рдд рд╣реИ, рддреЛ рдЖрдк рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рдЕрд▓рдВрдХреГрдд рдХрд░рдирд╛ рдФрд░ рдмрдирд╛рдирд╛ рд╢реБрд░реВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рдкрд╛рдВрдЪ рдЪрд░рдг - рд╕рдЬрд╛рд╡рдЯ рдФрд░ рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ред


Html рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЧреЛ рдкреИрдХреЗрдЬ рдореЗрдВ рдПрдХ рдкреВрд░реА рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╣реИ, рд▓реЗрдХрд┐рди рд╣рдо рдПрдХ рддрдерд╛рдХрдерд┐рдд рд╕рд┐рдВрдЧрд▓-рдкреЗрдЬ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдПрдВрдЧреЗ рдЬреЛ рд╕реАрдзреЗ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред рдЗрд╕ AngularJS рдХреЗ рд╕рд╛рде рд╣рдорд╛рд░реА рдорджрдж рдХрд░реЗрдВред

рдирдП рдЪрд░рдг рдХреЗ рд▓рд┐рдП рдХреЛрдб рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛:

 > git checkout step-5 

рдЬреИрд╕рд╛ рдХрд┐ рдкрд╣рд▓реЗ рдЕрдзреНрдпрд╛рдп рдореЗрдВ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдорд╛рд░реНрдЯрд┐рдиреА рдХреЗ рдкрд╛рд╕ рд╕реНрдЯреИрдЯрд┐рдХреНрд╕ рд╡рд┐рддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╣реИрдВрдбрд▓рд░ рд╣реИ, рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдпрд╣ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕реЗ рд╕реНрдерд┐рд░ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рд╡рд┐рддрд░рд┐рдд рдХрд░рддрд╛ рд╣реИред рд╣рдордиреЗ рд╡рд╣рд╛рдВ рдЬрд░реВрд░реА js рдФрд░ css рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд▓рдЧрд╛рдИрдВред рдореИрдВ рдлреНрд░рдВрдЯ-рдПрдВрдб рдХреЗ рдХрд╛рдо рдХрд╛ рд╡рд░реНрдгрди рдХрд░реВрдВрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╣рдорд╛рд░реЗ рд▓реЗрдЦ рдХрд╛ рд▓рдХреНрд╖реНрдп рдирд╣реАрдВ рд╣реИ, рдЖрдк рд╕реНрд╡рдпрдВ рд╕реНрд░реЛрдд рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдХреЛрдгреАрдп рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП, рд╡рд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИред

рдореБрдЦреНрдп рдкреГрд╖реНрда рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рдЕрд▓рдЧ рд╣реИрдВрдбрд▓рд░ рдЬреЛрдбрд╝реЗрдВрдЧреЗ:

  api.Get("**", func(r render.Render){ r.HTML(200, "index", nil) }) 


рдЧреНрд▓реЛрдм ** рдЕрдХреНрд╖рд░ рдХрд╣рддреЗ рд╣реИрдВ рдХрд┐ рдХрд┐рд╕реА рднреА рдкрддреЗ рдХреЗ рд▓рд┐рдП рдПрдХ index.html рдлрд╝рд╛рдЗрд▓ рд╡рд╛рдкрд╕ рдХрд░ рджреА рдЬрд╛рдПрдЧреАред рд╕рд╣реА рддрд░реАрдХреЗ рд╕реЗ рдЯреЗрдореНрдкреНрд▓реЗрдЯ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдордиреЗ рдПрдХ рд░реЗрдВрдбрд░рд░ рдмрдирд╛рддреЗ рд╕рдордп рд╡рд┐рдХрд▓реНрдк рдЬреЛрдбрд╝реЗ рдЬреЛ рдпрд╣ рджрд░реНрд╢рд╛рддрд╛ рд╣реИ рдХрд┐ рдЯреЗрдореНрдкреНрд▓реЗрдЯ рдХрд╣рд╛рдБ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВред рдкреНрд▓рд╕, рддрд╛рдХрд┐ рдХреЛрдгреАрдп рдЯреЗрдореНрдкрд▓реЗрдЯреНрд╕ рдХреЗ рд╕рд╛рде рдХреЛрдИ рд╕рдВрдШрд░реНрд╖ рди рд╣реЛ, {{}} рд╕реЗ {[{}]} рдкрд░ рдкреБрди: рдЕрд╕рд╛рдЗрди рдХрд░реЗрдВред

  api.Use(render.Renderer(render.Options{ Directory: "public/static/views", Extensions: []string{".html"}, Delims: render.Delims{"{[{", "}]}"}, })) 


рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд░рдВрдЧ рдХреНрд╖реЗрддреНрд░ (рддреАрди рдмрд╛рдЗрдЯреНрд╕ рдЬреЛ рдЖрд░рдЬреАрдмреА рд░рдВрдЧ рдорд╛рди рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддреЗ рд╣реИрдВ) рдФрд░ рдлрд╝реЗрд╡рд┐рдХреЙрди (рдбреЗрдЯрд╛ рдЙрд░реА рдЪрд┐рддреНрд░, рдЖрдкрдХреЛ рд░рдВрдЧреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ) рдХреЛ рдмрд┐рди рдореЙрдбрд▓ рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛, рдЬреЛ рд░рдВрдЧреЛрдВ рдХреЗ рд╕рд╛рде рдЕрд▓рдЧ-рдЕрд▓рдЧ рдмрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдЕрд▓рдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд╕реНрддреБ рдмрдирд╛рддреЗ рд╕рдордп рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рд░реВрдк рд╕реЗ рдЙрддреНрдкрдиреНрди рд╣реЛрддреЗ рд╣реИрдВред

 type Bin struct { ... Color [3]byte `json:"color"` Favicon string `json:"favicon"` } func NewBin() *Bin { color:= RandomColor() bin := Bin{ ... Color: color, Favicon: Solid16x16gifDatauri(color), } ... } 

рдЕрдм рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд▓рдЧрднрдЧ рдкреВрд░реНрдг рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рд╡рд╛рд▓рд╛ рд╡реЗрдм рдЕрдиреБрдкреНрд░рдпреЛрдЧ рд╣реИ, рдЖрдк рдЗрд╕реЗ рдЪрд▓рд╛ рд╕рдХрддреЗ рд╣реИрдВ:

 > go run ./src/main.go 

рдФрд░ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЦреЛрд▓реЗрдВ ( 127.0.0.1:3000 127.0.0.1:3000 ) рдХреЗ рдЖрд╕рдкрд╛рд╕ рдЦреЗрд▓рдиреЗ рдХреЗ рд▓рд┐рдПред

рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдЕрднреА рднреА рджреЛ рд╕рдорд╕реНрдпрд╛рдПрдВ рд╣реИрдВ: рдХрд╛рд░реНрдпрдХреНрд░рдо рд╕рдорд╛рдкреНрдд рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж, рд╕рднреА рдбреЗрдЯрд╛ рдЦреЛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдмреАрдЪ рдХреЛрдИ рдЕрд▓рдЧрд╛рд╡ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рд╣рд░ рдХреЛрдИ рдПрдХ рд╣реА рдЪреАрдЬ рджреЗрдЦрддрд╛ рд╣реИред рдЕрдЪреНрдЫрд╛, рдЪрд▓реЛ рдХрд░рддреЗ рд╣реИрдВред

рдЪрд░рдг рдЫрд╣ рдХреБрдЫ рдЧреЛрдкрдиреАрдпрддрд╛ рдЬреЛрдбрд╝реЗрдВред

рдЫрдареЗ рдЪрд░рдг рдХреЗ рд▓рд┐рдП рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ:

 > git checkout step-6 

рд╣рдо рд╕рддреНрд░реЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдПрдХ-рджреВрд╕рд░реЗ рд╕реЗ рдЕрд▓рдЧ рдХрд░реЗрдВрдЧреЗред рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрдиреНрд╣реЗрдВ рдХрд╣рд╛рдВ рд╕реНрдЯреЛрд░ рдХрд░рдирд╛ рд╣реИ, рдпрд╣ рдЪреБрдиреЗрдВред рдорд╛рд░реНрдЯрд┐рдиреА-рдХрдВрдЯреНрд░реАрдм рдореЗрдВ рд╕рддреНрд░ рдЧреЛрд░рд┐рд▓реНрд▓рд╛ рд╡реЗрдм рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╕рддреНрд░ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИрдВред
рдЧреЛрд░рд┐рд▓реНрд▓рд╛ рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдХрд░рдгреЛрдВ рдХрд╛ рдПрдХ рд╕рдореВрд╣ рд╣реИред рдпреЗ рд╕рднреА рдЙрдкрдХрд░рдг рд╢рд┐рдерд┐рд▓ рд░реВрдк рд╕реЗ рдЬреБрдбрд╝реЗ рд╣реБрдП рд╣реИрдВ, рдЬреЛ рдЖрдкрдХреЛ рдХрд┐рд╕реА рднреА рд╣рд┐рд╕реНрд╕реЗ рдХреЛ рд▓реЗрдиреЗ рдФрд░ рдЦреБрдж рдХреЛ рдПрдореНрдмреЗрдб рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рдпрд╣ рд╣рдореЗрдВ рдЧреЛрд░рд┐рд▓реНрд▓рд╛ рдореЗрдВ рдкрд╣рд▓реЗ рд╕реЗ рд▓рд╛рдЧреВ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рд╣рдорд╛рд░рд╛ рдХреБрдХреА рдЖрдзрд╛рд░рд┐рдд рд╣реЛрдЧрд╛ред

рдПрдХ рд╕рддреНрд░ рднрдВрдбрд╛рд░ рдмрдирд╛рдПрдБ:

 func GetApi(config *Config) *martini.ClassicMartini { ... store := sessions.NewCookieStore([]byte(config.SessionSecret)) ... 

NewCookieStore рдлрд╝рдВрдХреНрд╢рди рдкреНрд░рдореБрдЦ рдЬреЛрдбрд╝реЗ рдХреЛ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд░реВрдк рдореЗрдВ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИ, рдЬреЛрдбрд╝реА рдореЗрдВ рдкрд╣рд▓реА рдХреБрдВрдЬреА рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИ, рдФрд░ рджреВрд╕рд░рд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдХреЗ рд▓рд┐рдПред рджреВрд╕рд░реА рдХреБрдВрдЬреА рдХреЛ рдЫреЛрдбрд╝ рджрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рд╕рддреНрд░реЛрдВ рдХреЛ рдЦреЛрдП рдмрд┐рдирд╛ рдЪрд╛рдмрд┐рдпреЛрдВ рдХреЛ рдШреБрдорд╛рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк рдХрдИ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдЬреЛрдбрд╝реЗ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд╕рддреНрд░ рдмрдирд╛рддреЗ рд╕рдордп, рдкрд╣рд▓реА рдЬреЛрдбрд╝реА рдХреА рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛, рд▓реЗрдХрд┐рди рдбреЗрдЯрд╛ рдХреА рдЬрд╛рдВрдЪ рдХрд░рддреЗ рд╕рдордп, рд╕рднреА рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреНрд░рдо рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдкрд╣рд▓реА рдЬреЛрдбрд╝реА рд╕реЗ рд╢реБрд░реВ рд╣реЛрддрд╛ рд╣реИред

рдЪреВрдВрдХрд┐ рд╣рдореЗрдВ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдЕрд▓рдЧ-рдЕрд▓рдЧ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдЗрд╕ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЛ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рд░рдЦреЗрдВрдЧреЗ, рдЬреЛ рднрд╡рд┐рд╖реНрдп рдореЗрдВ рд╣рдореЗрдВ рдкрд░реНрдпрд╛рд╡рд░рдг рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдиреЗ рдпрд╛ рдЭрдВрдбреЗ рд▓реЙрдиреНрдЪ рдХрд░рдиреЗ рдореЗрдВ рдорджрдж рдХрд░реЗрдЧрд╛ред

рд╣рдорд╛рд░реЗ рдПрдкреАрдЖрдИ рдореЗрдВ рдПрдХ рдордзреНрдпрд╡рд░реНрддреА рд╣реИрдВрдбрд▓рд░ рдЬреЛрдбрд╝реЗрдВ рдЬреЛ рд╕рддреНрд░реЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдЬреЛрдбрд╝рддрд╛ рд╣реИ:

 // Sessions is a Middleware that maps a session.Session service into the Martini handler chain. // Sessions can use a number of storage solutions with the given store. func Sessions(name string, store Store) martini.Handler { return func(res http.ResponseWriter, r *http.Request, c martini.Context, l *log.Logger) { // Map to the Session interface s := &session{name, r, l, store, nil, false} c.MapTo(s, (*Session)(nil)) // Use before hook to save out the session rw := res.(martini.ResponseWriter) rw.Before(func(martini.ResponseWriter) { if s.Written() { check(s.Session().Save(r, res), l) } }) ... c.Next() } } 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдХреЛрдб рд╕реЗ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдкреНрд░рддреНрдпреЗрдХ рдЕрдиреБрд░реЛрдз рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рддреНрд░ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдЕрдиреБрд░реЛрдз рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИред рдЕрдиреБрд░реЛрдз рдХреЗ рдЕрдВрдд рдореЗрдВ, рдмрдлрд░ рд╕реЗ рдбреЗрдЯрд╛ рд▓рд┐рдЦреЗ рдЬрд╛рдиреЗ рд╕реЗ рдареАрдХ рдкрд╣рд▓реЗ, рд╕рддреНрд░ рдбреЗрдЯрд╛ рд╕рд╣реЗрдЬрд╛ рдЬрд╛рддрд╛ рд╣реИ рдпрджрд┐ рдЗрд╕реЗ рдмрджрд▓ рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реЛред

рдЕрдм рд╣рдо рдЕрдкрдиреЗ рдЗрддрд┐рд╣рд╛рд╕ (рдЬреЛ рд╕рд┐рд░реНрдл рдПрдХ рдЯреБрдХрдбрд╝рд╛ рд╣реБрдЖ рдХрд░рддрд╛ рдерд╛) рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрддреЗ рд╣реИрдВ, history.go file:

 type History interface { All() []string Add(string) } type SessionHistory struct { size int name string session sessions.Session data []string } func (history *SessionHistory) All() []string { if history.data == nil { history.load() } return history.data } func (history *SessionHistory) Add(name string) { if history.data == nil { history.load() } history.data = append(history.data, "") copy(history.data[1:], history.data) history.data[0] = name history.save() } func (history *SessionHistory) save() { size := history.size if size > len(history.data){ size = len(history.data) } history.session.Set(history.name, history.data[:size]) } func (history *SessionHistory) load() { sessionValue := history.session.Get(history.name) history.data = []string{} if sessionValue != nil { if values, ok := sessionValue.([]string); ok { history.data = append(history.data, values...) } } } func NewSessionHistoryHandler(size int, name string) martini.Handler { return func(c martini.Context, session sessions.Session) { history := &SessionHistory{size: size, name: name, session: session} c.MapTo(history, (*History)(nil)) } } 

NewSessionHistoryHandler рд╡рд┐рдзрд┐ рдореЗрдВ, рд╣рдо рдПрдХ SessionHistory рдСрдмреНрдЬреЗрдХреНрдЯ рдмрдирд╛рддреЗ рд╣реИрдВ рдЬреЛ рдЗрддрд┐рд╣рд╛рд╕ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ (рд╕рднреА рдЗрддрд┐рд╣рд╛рд╕ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рдЬреЛрдбрд╝ рдФрд░ рдХреНрд╡реЗрд░реА рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реБрдП) рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдлрд┐рд░ рдЗрд╕реЗ рдкреНрд░рддреНрдпреЗрдХ рдЕрдиреБрд░реЛрдз рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдЬреЛрдбрд╝ рджреЗрддрд╛ рд╣реИред SessionHistory рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рд╕рд╣рд╛рдпрдХ рддрд░реАрдХреЗ рд▓реЛрдб рдФрд░ рд╕реЗрд╡ рд╣реЛрддреЗ рд╣реИрдВ, рдЬреЛ рд╕рддреНрд░ рдореЗрдВ рдбреЗрдЯрд╛ рдХреЛ рд▓реЛрдб рдФрд░ рд╕реЗрд╡ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╕рддреНрд░ рд╕реЗ рдбреЗрдЯрд╛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдирд╛ рдХреЗрд╡рд▓ рдорд╛рдВрдЧ рдкрд░ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЕрдм, рд╕рднреА рдПрдкреАрдЖрдИ рд╡рд┐рдзрд┐рдпреЛрдВ рдореЗрдВ рдЬрд╣рд╛рдВ рдЗрддрд┐рд╣рд╛рд╕ рдЯреБрдХрдбрд╝рд╛ рдкрд╣рд▓реЗ рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдЗрддрд┐рд╣рд╛рд╕ рдкреНрд░рдХрд╛рд░ рдХреА рдПрдХ рдирдИ рд╡рд╕реНрддреБ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред

рдЗрд╕ рдХреНрд╖рдг рд╕реЗ, рдкреНрд░рддреНрдпреЗрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХрд╛ рдмрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХрд╛ рдЕрдкрдирд╛ рдЗрддрд┐рд╣рд╛рд╕ рд╣реЛрдЧрд╛, рд▓реЗрдХрд┐рди рдПрдХ рд╕реАрдзрд╛ рд▓рд┐рдВрдХ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд╣рдо рдЕрднреА рднреА рдХрд┐рд╕реА рднреА рдмрд┐рди рдХреЛ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВред рд╣рдо рдЗрд╕реЗ рдирд┐рдЬреА рдмрд┐рди рдСрдмреНрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝рдХрд░ рдареАрдХ рдХрд░реЗрдВрдЧреЗред

рдЪрд▓реЛ рдмрд┐рди рдореЗрдВ рджреЛ рдирдП рдХреНрд╖реЗрддреНрд░ рдмрдирд╛рддреЗ рд╣реИрдВ:

 type Bin struct { ... Private bool `json:"private"` SecretKey string `json:"-"` } 

рд╕реАрдХреНрд░реЗрдЯрдХреЗ рдлреАрд▓реНрдб рдореЗрдВ рдПрдХ рдХреБрдВрдЬреА рд╕рдВрдЧреНрд░рд╣реАрдд рдХреА рдЬрд╛рдПрдЧреА, рдЬреЛ рдирд┐рдЬреА рдмрд┐рди рддрдХ рдкрд╣реБрдВрдЪ рдкреНрд░рджрд╛рди рдХрд░рддреА рд╣реИ (рдЬрд╣рд╛рдВ рдирд┐рдЬреА рдзреНрд╡рдЬ рд╕рд╣реА рдкрд░ рд╕реЗрдЯ рд╣реИ)ред рд╡рд╣ рд╡рд┐рдзрд┐ рдЬреЛрдбрд╝реЗрдВ рдЬреЛ рд╣рдорд╛рд░реА рд╡рд╕реНрддреБ рдХреЛ рдирд┐рдЬреА рдмрдирд╛рддреА рд╣реИ:

 func (bin *Bin) SetPrivate() { bin.Private = true bin.SecretKey = rs.Generate(32) } 

рдирд┐рдЬреА рдмрд┐рди рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░рд╛ рдЙрджреНрджреЗрд╢реНрдп, рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рдмрдирд╛рддреЗ рд╕рдордп, рдирд┐рдЬреА рдЭрдВрдбреЗ рдХреЗ рд╕рд╛рде рдПрдХ рдЬреЛрдВрд╕ рдСрдмреНрдЬреЗрдХреНрдЯ рднреЗрдЬреЗрдЧрд╛ред рдЗрдирдХрдорд┐рдВрдЧ рдЬреЛрдВрд╕ рдХреЛ рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдордиреЗ рдПрдХ рдЫреЛрдЯреА рдбрд┐рдХреЛрдбреЗрдЬрд╕рдирдкрд╛рдпреЛрдб рд╡рд┐рдзрд┐ рд▓рд┐рдЦреА рдЬреЛ рдЕрдиреБрд░реЛрдз рдмреЙрдбреА рдХреЛ рдкрдврд╝рддреА рд╣реИ рдФрд░ рдЗрд╕реЗ рдЙрд╕ рд╕рдВрд░рдЪрдирд╛ рдореЗрдВ рдЕрдирдкреИрдХ рдХрд░ рджреЗрддреА рд╣реИ рдЬрд┐рд╕рдХреА рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:

 func DecodeJsonPayload(r *http.Request, v interface{}) error { content, err := ioutil.ReadAll(r.Body) r.Body.Close() if err != nil { return err } err = json.Unmarshal(content, v) if err != nil { return err } return nil } 

рдЕрдм рд╣рдо рдирдП рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдкреАрдЖрдИ рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░реЗрдВрдЧреЗ:

  api.Post("/api/v1/bins/", func(r render.Render, storage Storage, history History, session sessions.Session, req *http.Request){ payload := Bin{} if err := DecodeJsonPayload(req, &payload); err != nil { r.JSON(400, ErrorMsg{fmt.Sprintf("Decoding payload error: %s", err)}) return } bin := NewBin() if payload.Private { bin.SetPrivate() } if err := storage.CreateBin(bin); err == nil { history.Add(bin.Name) if bin.Private { session.Set(fmt.Sprintf("pr_%s", bin.Name), bin.SecretKey) } r.JSON(http.StatusCreated, bin) } else { r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()}) } }) 

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдкреНрд░рдХрд╛рд░ рдмрд┐рди рдХреЗ рдПрдХ рдкреЗрд▓реЛрдб рдСрдмреНрдЬреЗрдХреНрдЯ рдмрдирд╛рддреЗ рд╣реИрдВ, рдЬрд┐рдирдореЗрдВ рд╕реЗ рдлрд╝реАрд▓реНрдб рдЕрдиреБрд░реЛрдз рдмреЙрдбреА рд╕реЗ DecodeJsonPayload рдлрд╝рдВрдХреНрд╢рди рдореЗрдВ рдорд╛рдиреЛрдВ рд╕реЗ рднрд░реЗ рд╣реЛрдВрдЧреЗред рдЙрд╕рдХреЗ рдмрд╛рдж, рдпрджрд┐ рд╡рд┐рдХрд▓реНрдк "рдирд┐рдЬреА" рдЗрдирдкреБрдЯ рдореЗрдВ рд╕реЗрдЯ рд╣реИ, рддреЛ рд╣рдо рдЕрдкрдиреЗ рдмрд┐рди рдХреЛ рдирд┐рдЬреА рдмрдирд╛рддреЗ рд╣реИрдВред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдирд┐рдЬреА рд╡рд╕реНрддреБрдУрдВ рдХреЗ рд▓рд┐рдП, рд╣рдо рд╕рддреНрд░ рд╕рддреНрд░ рдореЗрдВ рдкреНрд░рдореБрдЦ рдореВрд▓реНрдп рдХреЛ session.Set(fmt.Sprintf("pr_%s", bin.Name), bin.SecretKey) ред рдЕрдм рд╣рдореЗрдВ рдЕрдиреНрдп рдПрдкреАрдЖрдИ рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЛ рдмрджрд▓рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рддрд╛рдХрд┐ рд╡реЗ рдирд┐рдЬреА рдмрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЗ рд▓рд┐рдП рд╕рддреНрд░ рдореЗрдВ рдХреБрдВрдЬреА рдХреЗ рдЕрд╕реНрддрд┐рддреНрд╡ рдХреА рдЬрд╛рдВрдЪ рдХрд░реЗрдВред

рдпрд╣ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ:

  api.Get("/api/v1/bins/:bin", func(r render.Render, params martini.Params, session sessions.Session, storage Storage){ if bin, err := storage.LookupBin(params["bin"]); err == nil{ if bin.Private && bin.SecretKey != session.Get(fmt.Sprintf("pr_%s", bin.Name)){ r.JSON(http.StatusForbidden, ErrorMsg{"The bin is private"}) } else { r.JSON(http.StatusOK, bin) } } else { r.JSON(http.StatusNotFound, ErrorMsg{err.Error()}) } }) 

рд╕рд╛рджреГрд╢реНрдп рджреНрд╡рд╛рд░рд╛, рдЕрдиреНрдп рддрд░реАрдХреЛрдВ рд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдирдП рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рдкрд░реАрдХреНрд╖рдг рднреА рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд┐рдП рдЧрдП рдереЗ, рдХреЛрдб рдореЗрдВ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдкрд░рд┐рд╡рд░реНрддрди рджреЗрдЦреЗ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред

рдпрджрд┐ рдЖрдк рд╣рдорд╛рд░реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдЕрдм рд╡рд┐рднрд┐рдиреНрди рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдпрд╛ рдЧреБрдкреНрдд рдореЛрдб рдореЗрдВ рдЪрд▓рд╛рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдЗрддрд┐рд╣рд╛рд╕ рдЕрд▓рдЧ рд╣реИ, рдФрд░ рдХреЗрд╡рд▓ рд╡рд╣ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдЬрд┐рд╕рдореЗрдВ рдЗрд╕реЗ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рдирд┐рдЬреА рдмрд┐рди рдСрдмреНрдЬреЗрдХреНрдЯреНрд╕ рддрдХ рдкрд╣реБрдВрдЪ рд╣реИред

рд╕рдм рдХреБрдЫ рдареАрдХ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрдм рд╣рдорд╛рд░реЗ рднрдВрдбрд╛рд░рдг рдореЗрдВ рд╕рднреА рдСрдмреНрдЬреЗрдХреНрдЯ рд▓рдЧрднрдЧ рд╣рдореЗрд╢рд╛ рдХреЗ рд▓рд┐рдП рд░рд╣рддреЗ рд╣реИрдВ, рдЬреЛ рд╢рд╛рдпрдж рд╕рд╣реА рдирд╣реАрдВ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдХреЛрдИ рд╢рд╛рд╢реНрд╡рдд рд╕реНрдореГрддрд┐ рдирд╣реАрдВ рд╣реЛ рд╕рдХрддреА рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдЙрдирдХреЗ рдЬреАрд╡рди рдХреЗ рд╕рдордп рдХреЛ рд╕реАрдорд┐рдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВрдЧреЗред

рд╕рд╛рддрд╡рд╛рдВ рдЪрд░рдгред рд╣рдо рдЕрдирд╛рд╡рд╢реНрдпрдХ рд╕рд╛рдл рдХрд░рддреЗ рд╣реИрдВред



рд╕рд╛рддрд╡рд╛рдВ рдЪрд░рдг рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ:

 git checkout step-7 

рдЖрдзрд╛рд░ рд╕рдВрдЧреНрд░рд╣рдг рд╕рдВрд░рдЪрдирд╛ рдореЗрдВ рдПрдХ рдФрд░ рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝реЗрдВ:

 type BaseStorage struct { ... binLifetime int64 } 

рдпрд╣ рдмрд┐рди рдСрдмреНрдЬреЗрдХреНрдЯ рдФрд░ рд╕рдВрдмрдВрдзрд┐рдд рдкреНрд░рд╢реНрдиреЛрдВ рдХреЗ рдЕрдзрд┐рдХрддрдо рдЬреАрд╡рдирдХрд╛рд▓ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░реЗрдЧрд╛ред рдЕрдм рд╣рдо рдореЗрдореЛрд░реА рдореЗрдВ рдЕрдкрдиреЗ рд╕реНрдЯреЛрд░реЗрдЬ рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрддреЗ рд╣реИрдВ - memory.goред рдмрд┐рдирд▓рд╛рдЗрди рд▓рд╛рдЗрдлрдЯрд╛рдЗрдо рд╕реЗрдХрдВрдб рд╕реЗ рдЕрдзрд┐рдХ рдХреЗ рд▓рд┐рдП рдЕрдкрдбреЗрдЯ рдирд╣реАрдВ рдХрд┐рдП рдЧрдП рд╕рднреА рдмрд┐рдирдХрд╛рд░реНрдб рдХреЛ рд╕рд╛рдлрд╝ рдХрд░рдиреЗ рдХреА рдореБрдЦреНрдп рд╡рд┐рдзрд┐:

 func (storage *MemoryStorage) clean() { storage.Lock() defer storage.Unlock() now := time.Now().Unix() for name, binRecord := range storage.binRecords { if binRecord.bin.Updated < (now - storage.binLifetime) { delete(storage.binRecords, name) } } } 

рд╣рдо рдЗрд╕рдХреЗ рд╕рд╛рде рдореЗрдореЛрд░реАрд╕реНрдЯреЗрдЬ рдкреНрд░рдХрд╛рд░ рдореЗрдВ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЯрд╛рдЗрдорд░ рдФрд░ рддрд░реАрдХреЗ рднреА рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:

 type MemoryStorage struct { ... cleanTimer *time.Timer } func (storage *MemoryStorage) StartCleaning(timeout int) { defer func(){ storage.cleanTimer = time.AfterFunc(time.Duration(timeout) * time.Second, func(){storage.StartCleaning(timeout)}) }() storage.clean() } func (storage *MemoryStorage) StopCleaning() { if storage.cleanTimer != nil { storage.cleanTimer.Stop() } } 


рдкреИрдХреЗрдЬ рд╡рд┐рдзрд┐ рд╕рдордп AfterFunc рдПрдХ рдЕрд▓рдЧ рдЧреЛрд░реЛрдЗрди рдореЗрдВ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдлрд╝рдВрдХреНрд╢рди рд╢реБрд░реВ рдХрд░рддрд╛ рд╣реИ (рдпрд╣ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рдмрд┐рдирд╛ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдЗрд╕рд▓рд┐рдП, рд╣рдо рдЯрд╛рдЗрдордЖрдЙрдЯ рдкрд╛рд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдпрд╣рд╛рдВ рдХреНрд▓реЛрдЬрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ), рдЬреИрд╕реЗ рд╕рдордп рд╕рдорд╛рдкреНрдд рд╣реЛрдиреЗ рдХреЗ рдмрд╛рджред рдкрд╣рд▓рд╛ рддрд░реНрдХ рдореЗрдВ рдкрд╛рд░рд┐рддред

рд╣рдорд╛рд░реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рдХреНрд╖реИрддрд┐рдЬ рд╕реНрдХреЗрд▓рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП, рдЗрд╕реЗ рд╡рд┐рднрд┐рдиреНрди рд╕рд░реНрд╡рд░реЛрдВ рдкрд░ рдЪрд▓рд╛рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реЛрдЧрд╛, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЕрдкрдиреЗ рдбреЗрдЯрд╛ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрд▓рдЧ рд╕реНрдЯреЛрд░реЗрдЬ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП рд░реЗрдбрд┐рд╕ рд▓реЗрдВред

рдЪрд░рдг рдЖрдаред рд╣рдо рднрдВрдбрд╛рд░рдг рдХреЗ рд▓рд┐рдП рд░реЗрдбрд┐рд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред


рдЖрдзрд┐рдХрд╛рд░рд┐рдХ Redis рдкреНрд░рд▓реЗрдЦрди рдЧреЛ рдХреЗ рд▓рд┐рдП рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХреА рдПрдХ рд╡рд┐рд╕реНрддреГрдд рд╕реВрдЪреА рдкрд░ рд╣рдореЗрдВ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реИред рд▓реЗрдЦрди рдХреЗ рд╕рдордп, рдЕрдиреБрд╢рдВрд╕рд┐рдд рдореВрд▓рд╛рдВрдХ рдФрд░ рд░реЗрдбрд┐рдЧреЛ рд╣реИрдВ ред рд╣рдо рд░реЗрдбрд┐рдЧреЛ рдХрд╛ рдЪрдпрди рдХрд░реЗрдВрдЧреЗ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рд╡рд┐рдХрд╕рд┐рдд рд╣реЛ рд░рд╣рд╛ рд╣реИ рдФрд░ рдЗрд╕рдореЗрдВ рдПрдХ рдмрдбрд╝рд╛ рд╕рдореБрджрд╛рдп рд╣реИред

рдЪрд▓реЛ рд╡рд╛рдВрдЫрд┐рдд рдХреЛрдб рдкрд░ рдЪрд▓рддреЗ рд╣реИрдВ:

 git checkout step-8 

Redis.go рдлрд╝рд╛рдЗрд▓ рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВ, рдФрд░ рдпрд╣ Redis рдХреЗ рд▓рд┐рдП рд╕рдВрдЧреНрд░рд╣рдг рдХрд╛ рд╣рдорд╛рд░рд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реЛрдЧрд╛ред рдореВрд▓ рд╕рдВрд░рдЪрдирд╛ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ:

 type RedisStorage struct { BaseStorage pool *redis.Pool prefix string cleanTimer *time.Timer } 

рдкреВрд▓ рдореЗрдВ, рдореВрд▓реА рдХреЗ рд▓рд┐рдП рдХрдиреЗрдХреНрд╢рди рдХреЗ рдкреВрд▓ рдХреЛ рдЙрдкрд╕рд░реНрдЧ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ - рд╕рднреА рдХреБрдВрдЬреА рдХреЗ рд▓рд┐рдП рд╕рд╛рдорд╛рдиреНрдп рдЙрдкрд╕рд░реНрдЧред рдПрдХ рдкреВрд▓ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, redigo рдЙрджрд╛рд╣рд░рдгреЛрдВ рд╕реЗ рдХреЛрдб рд▓реЗрдВ:

 func getPool(server string, password string) (pool *redis.Pool) { pool = &redis.Pool{ MaxIdle: 3, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial("tcp", server) if err != nil { return nil, err } if password != "" { if _, err := c.Do("AUTH", password); err != nil { c.Close() return nil, err } } return c, err }, TestOnBorrow: func(c redis.Conn, _ time.Time) error { _, err := c.Do("PING") return err }, } return pool } 

рдбрд╛рдпрд▓ рдореЗрдВ, рд╣рдо рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдкрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ рдХрд┐ рд░реЗрдбрд┐рд╕ рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж, рдкрд╛рд╕рд╡рд░реНрдб рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдкрд░ рд▓реЙрдЧ рдЗрди рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдЧрд╛ред рдЙрд╕рдХреЗ рдмрд╛рдж, рд╕реНрдерд╛рдкрд┐рдд рдХрдиреЗрдХреНрд╢рди рд╡рд╛рдкрд╕ рдЖ рдЧрдпрд╛ рд╣реИред TestOnBreat рдлрд╝рдВрдХреНрд╢рди рдХреЛ рдкреВрд▓ рд╕реЗ рдХрдиреЗрдХреНрд╢рди рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рдиреЗ рдкрд░ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЗрд╕рдореЗрдВ рдЖрдк рд╡реНрдпрд╡рд╣рд╛рд░реНрдпрддрд╛ рдХреЗ рд▓рд┐рдП рдХрдиреЗрдХреНрд╢рди рдХреА рдЬрд╛рдВрдЪ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рджреВрд╕рд░рд╛ рдкреИрд░рд╛рдореАрдЯрд░ рд╡рд╣ рд╕рдордп рд╣реИ рдЬрдм рдХрдиреЗрдХреНрд╢рди рдкреВрд▓ рдореЗрдВ рд▓реМрдЯрд╛рдпрд╛ рдЧрдпрд╛ рдерд╛ред рд╣рдо рдмрд╕ рд╣рд░ рдмрд╛рд░ рдкрд┐рдВрдЧ рднреЗрдЬрддреЗ рд╣реИрдВред

рдкреИрдХреЗрдЬ рдореЗрдВ рднреА рд╣рдордиреЗ рдХрдИ рд╕реНрдерд┐рд░рд╛рдВрдХ рдШреЛрд╖рд┐рдд рдХрд┐рдП рд╣реИрдВ:

 const ( KEY_SEPARATOR = "|" //   BIN_KEY = "bins" //     Bin REQUESTS_KEY = "rq" //      REQUEST_HASH_KEY = "rhsh" //        CLEANING_SET = "cln" // ,      Bin   CLEANING_FACTOR = 3 //      ) 

рд╣рдореЗрдВ рдЗрд╕ рдкреИрдЯрд░реНрди рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдХреБрдВрдЬрд┐рдпрд╛рдБ рдорд┐рд▓рддреА рд╣реИрдВ:

 func (storage *RedisStorage) getKey(keys ...string) string { return fmt.Sprintf("%s%s%s", storage.prefix, KEY_SEPARATOR, strings.Join(keys, KEY_SEPARATOR)) } 


рд╣рдорд╛рд░реЗ рдбреЗрдЯрд╛ рдХреЛ рдореВрд▓реА рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрдиреНрд╣реЗрдВ рдХреБрдЫ рдХреЗ рд╕рд╛рде рдХреНрд░рдордмрджреНрдз рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╣рдо рд▓реЛрдХрдкреНрд░рд┐рдп msgpack рдкреНрд░рд╛рд░реВрдк рдХрд╛ рдЪрдпрди рдХрд░реЗрдВрдЧреЗ рдФрд░ рд▓реЛрдХрдкреНрд░рд┐рдп рдХреЛрдбреЗрдХ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред

рд╣рдо рдЙрди рддрд░реАрдХреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВ рдЬреЛ рдмрд╛рдЗрдирд░реА рдбреЗрдЯрд╛ рдореЗрдВ рд╕рдВрднрд╡ рд╣реИ рдФрд░ рдЗрд╕рдХреЗ рд╡рд┐рдкрд░реАрдд рд╕рдм рдХреБрдЫ рдХреЛ рдХреНрд░рдордмрджреНрдз рдХрд░рддреЗ рд╣реИрдВ:

 func (storage *RedisStorage) Dump(v interface{}) (data []byte, err error) { var ( mh codec.MsgpackHandle h = &mh ) err = codec.NewEncoderBytes(&data, h).Encode(v) return } func (storage *RedisStorage) Load(data []byte, v interface{}) error { var ( mh codec.MsgpackHandle h = &mh ) return codec.NewDecoderBytes(data, h).Decode(v) } 

рдЕрдм рд╣рдо рдЕрдиреНрдп рд╡рд┐рдзрд┐рдпреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВред

рдПрдХ рдмрд┐рди рд╡рд╕реНрддреБ рдХрд╛ рдирд┐рд░реНрдорд╛рдг

 func (storage *RedisStorage) UpdateBin(bin *Bin) (err error) { dumpedBin, err := storage.Dump(bin) if err != nil { return } conn := storage.pool.Get() defer conn.Close() key := storage.getKey(BIN_KEY, bin.Name) conn.Send("SET", key, dumpedBin) conn.Send("EXPIRE", key, storage.binLifetime) conn.Flush() return err } func (storage *RedisStorage) CreateBin(bin *Bin) error { if err := storage.UpdateBin(bin); err != nil { return err } return nil } 


рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдбрдВрдк рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдмрд┐рди рдХреЛ рдХреНрд░рдордмрджреНрдз рдХрд░рддреЗ рд╣реИрдВред рдлрд┐рд░ рд╣рдо рдкреВрд▓ рд╕реЗ рдореВрд▓реА рдпреМрдЧрд┐рдХ рд▓реЗрддреЗ рд╣реИрдВ (рдпрд╣ рднреВрд▓рдХрд░ рдХрд┐ рдЗрд╕реЗ defer рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╡рд╛рдкрд╕ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП)ред
Redigo рдкрд╛рдЗрдкрд▓рд╛рдЗрди рдореЛрдб рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рд╣рдо рднреЗрдЬреЗрдВ рд╡рд┐рдзрд┐ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдмрдлрд░ рдореЗрдВ рдПрдХ рдХрдорд╛рдВрдб рдЬреЛрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВ, рдлреНрд▓рд╢ рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдмрдлрд░ рд╕реЗ рд╕рднреА рдбреЗрдЯрд╛ рднреЗрдЬреЗрдВ рдФрд░ рдкрд░рд┐рдгрд╛рдо рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВред Do рдХрдорд╛рдВрдб рд╕рднреА рддреАрди рдЯреАрдореЛрдВ рдХреЛ рдПрдХ рдореЗрдВ рдЬреЛрдбрд╝рддреА рд╣реИред рдЖрдк рд░реЗрдбрд┐рдЧреЛ рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдЕрдзрд┐рдХ, рд▓реЗрди-рджреЗрди рдХреЛ рднреА рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рд╣рдо рдЕрдкрдиреЗ рдирд╛рдо рд╕реЗ рдмрд┐рди рдХреЗ рдбреЗрдЯрд╛ рдХреЛ рдмрдЪрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рдЖрджреЗрд╢, "рд╕реЗрдЯ" рднреЗрдЬрддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕ рд░рд┐рдХреЙрд░реНрдб рдХреЗ рдЬреАрд╡рдирдХрд╛рд▓ рдХреЛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рд╕рдордп рд╕реАрдорд╛ рд╕рдорд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВред

рдмрд┐рди рд╡рд╕реНрддреБ рдкрд╛рдирд╛

 func (storage *RedisStorage) LookupBin(name string) (bin *Bin, err error) { conn := storage.pool.Get() defer conn.Close() reply, err := redis.Bytes(conn.Do("GET", storage.getKey(BIN_KEY, name))) if err != nil { if err == redis.ErrNil { err = errors.New("Bin was not found") } return } err = storage.Load(reply, &bin) return } 

рд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐ redis.Bytes рдмрд╛рдЗрдЯ рд╕рд░рдгреА рдореЗрдВ conn.Do рд╕реЗ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЛ рдкрдврд╝рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреА рд╣реИред рдпрджрд┐ рдСрдмреНрдЬреЗрдХреНрдЯ рдирд╣реАрдВ рдорд┐рд▓рд╛, рддреЛ рдореВрд▓реА рд╡рд┐рд╢реЗрд╖ рддреНрд░реБрдЯрд┐ рдкреНрд░рдХрд╛рд░ redis.ErrNil рд╡рд╛рдкрд╕ рдХрд░ рджреЗрдЧреАред рдпрджрд┐ рд╕рдмрдХреБрдЫ рдареАрдХ рд╣реЛ рдЧрдпрд╛, рддреЛ рдбреЗрдЯрд╛ рдХреЛ рд▓реЛрдб рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдкрд╛рд░рд┐рдд рдХрд░рдХреЗ, рдмрд┐рди рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рд▓реЛрдб рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рдмрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреА рд╕реВрдЪреА рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛

 func (storage *RedisStorage) LookupBins(names []string) ([]*Bin, error) { bins := []*Bin{} if len(names) == 0 { return bins, nil } args := redis.Args{} for _, name := range names { args = args.Add(storage.getKey(BIN_KEY, name)) } conn := storage.pool.Get() defer conn.Close() if values, err := redis.Values(conn.Do("MGET", args...)); err == nil { bytes := [][]byte{} if err = redis.ScanSlice(values, &bytes); err != nil { return nil, err } for _, rawbin := range bytes { if len(rawbin) > 0 { bin := &Bin{} if err := storage.Load(rawbin, bin); err == nil { bins = append(bins, bin) } } } return bins, nil } else { return nil, err } } 

рдпрд╣рд╛рдВ, рд▓рдЧрднрдЧ рд╕рдм рдХреБрдЫ рдкрд┐рдЫрд▓реА рд╡рд┐рдзрд┐ рдХреЗ рд╕рдорд╛рди рд╣реИ, рд╕рд┐рд╡рд╛рдп рдЗрд╕рдХреЗ рдХрд┐ рдПрдордЬреАрдИрдЯреА рдХрдорд╛рдВрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдбреЗрдЯрд╛ рд╕реНрд▓рд╛рдЗрд╕ рдФрд░ рд░рд┐рдбрд┐рд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд╕реНрдХреЗрдиреНрд╕реНрд▓реАрд╕ рд╣реЗрд▓реНрдкрд░ рд╡рд┐рдзрд┐ рд╕реЗ рд╡рд╛рдВрдЫрд┐рдд рдкреНрд░рдХрд╛рд░ рдХреЗ рд╕реНрд▓рд╛рдЗрд╕ рдореЗрдВ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред

рдЕрдиреБрд░реЛрдз рдЕрдиреБрд░реЛрдз рдмрдирд╛рдирд╛

 func (storage *RedisStorage) CreateRequest(bin *Bin, req *Request) (err error) { data, err := storage.Dump(req) if err != nil { return } conn := storage.pool.Get() defer conn.Close() key := storage.getKey(REQUESTS_KEY, bin.Name) conn.Send("LPUSH", key, req.Id) conn.Send("EXPIRE", key, storage.binLifetime) key = storage.getKey(REQUEST_HASH_KEY, bin.Name) conn.Send("HSET", key, req.Id, data) conn.Send("EXPIRE", key, storage.binLifetime) conn.Flush() requestCount, err := redis.Int(conn.Receive()) if err != nil { return } if requestCount < storage.maxRequests { bin.RequestCount = requestCount } else { bin.RequestCount = storage.maxRequests } bin.Updated = time.Now().Unix() if requestCount > storage.maxRequests * CLEANING_FACTOR { conn.Do("SADD", storage.getKey(CLEANING_SET), bin.Name) } if err = storage.UpdateBin(bin); err != nil { return } return } 

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдмрд┐рди рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз рд╕реВрдЪреА рдореЗрдВ рдЕрдиреБрд░реЛрдз рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рдХреЛ рдмрдЪрд╛рддреЗ рд╣реИрдВред рдлрд┐рд░, рд╣рдо рдХреНрд░рдордмрджреНрдз рдЕрдиреБрд░реЛрдз рдХреЛ рд╣реИрд╢ рддрд╛рд▓рд┐рдХрд╛ рдореЗрдВ рд╕рд╣реЗрдЬрддреЗ рд╣реИрдВред рджреЛрдиреЛрдВ рдорд╛рдорд▓реЛрдВ рдореЗрдВ, рдЬреАрд╡рдирдХрд╛рд▓ рдЬреЛрдбрд╝рдирд╛ рди рднреВрд▓реЗрдВред LPUSH рдХрдорд╛рдВрдб рдЕрдиреБрд░реЛрдз рд╕реВрдЪреА рдореЗрдВ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐рдпреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рд▓реМрдЯрд╛рддрд╛ рд╣реИ, рдпрджрд┐ рдпрд╣ рд╕рдВрдЦреНрдпрд╛ рдХрд┐рд╕реА рдХрд╛рд░рдХ рджреНрд╡рд╛рд░рд╛ рдЕрдзрд┐рдХрддрдо рдЧреБрдгрд╛ рд╕реЗ рдЕрдзрд┐рдХ рд╣реЛ рдЬрд╛рддреА рд╣реИ, рддреЛ рдЗрд╕ рдмрд┐рди рдХреЛ рдЕрдЧрд▓реЗ рд╕рдлрд╛рдИ рдХреЗ рд▓рд┐рдП рдЙрдореНрдореАрджрд╡рд╛рд░реЛрдВ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВред

рдмрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЗ рд╕рд╛рде рд╕рд╛рджреГрд╢реНрдп рджреНрд╡рд╛рд░рд╛ рдЕрдиреБрд░реЛрдз рдХреА рдкреНрд░рд╛рдкреНрддрд┐ рдФрд░ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреА рд╕реВрдЪреА рддреИрдпрд╛рд░ рдХреА рдЬрд╛рддреА рд╣реИред

рд╕рдлрд╛рдИ

 func (storage *RedisStorage) clean() { for { conn := storage.pool.Get() defer conn.Close() binName, err := redis.String(conn.Do("SPOP", storage.getKey(CLEANING_SET))) if err != nil { break } conn.Send("LRANGE", storage.getKey(REQUESTS_KEY, binName), storage.maxRequests, -1) conn.Send("LTRIM", storage.getKey(REQUESTS_KEY, binName), 0, storage.maxRequests-1) conn.Flush() if values, error := redis.Values(conn.Receive()); error == nil { ids := []string{} if err := redis.ScanSlice(values, &ids); err != nil { continue } if len(ids) > 0 { args := redis.Args{}.Add(storage.getKey(REQUEST_HASH_KEY, binName)).AddFlat(ids) conn.Do("HDEL", args...) } } } } 

рдореЗрдореЛрд░реАрд╕реНрдЯреЛрд░реЗрдЬ рдХреЗ рд╡рд┐рдкрд░реАрдд, рдпрд╣рд╛рдВ рд╣рдо рдирд┐рд░рд░реНрдердХ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд╕реНрдкрд╖реНрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ рдЬреАрд╡рдирдХрд╛рд▓ EXPIRE рдореВрд▓реА рдХрдорд╛рдВрдб рджреНрд╡рд╛рд░рд╛ рд╕реАрдорд┐рдд рд╣реИред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рд╕рдлрд╛рдИ рдХреЗ рд▓рд┐рдП рд╕реВрдЪреА рд╕реЗ рдПрдХ рдЖрдЗрдЯрдо рд▓реЗрддреЗ рд╣реИрдВ, рдЗрд╕рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдзрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рд╕реЗ рдЕрдиреБрд░реЛрдз рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ рд╕реАрдорд╛ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдирд╣реАрдВ рд╣реИрдВ, рдФрд░ рд╕реВрдЪреА рдХреЛ рдЬрд┐рд╕ рдЖрдХрд╛рд░ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЙрд╕реЗ рд╕рдВрдкреАрдбрд╝рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП LTRIM рдХрдорд╛рдВрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред рд╣рдо HDEL рдХрдорд╛рдВрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд╣рд▓реЗ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛рдУрдВ рдХреЛ рд╣реИрд╢ рдЯреЗрдмрд▓ рд╕реЗ рд╣рдЯрд╛ рджреЗрддреЗ рд╣реИрдВ, рдЬреЛ рдПрдХ рд╣реА рдмрд╛рд░ рдореЗрдВ рдХрдИ рдХреБрдВрдЬреА рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИред

рд╣рдордиреЗ рд░реЗрдбрд┐рд╕рд╕реНрдЯреЛрд░реА рдХрд╛ рд╡рд░реНрдгрди рдХрд░рдирд╛ рд╕рдорд╛рдкреНрдд рдХрд░ рджрд┐рдпрд╛ рд╣реИ, рдЗрд╕рдХреЗ рдЖрдЧреЗ, redis_test.go рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рдЖрдкрдХреЛ рд╕рдорд╛рди рдкрд░реАрдХреНрд╖рдг рдорд┐рд▓реЗрдВрдЧреЗред

рдЕрдм, api.go рдлрд╝рд╛рдЗрд▓ рдореЗрдВ, рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдХреЛ рд╢реБрд░реВ рдХрд░рддреЗ рд╕рдордп рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХрд╛ рдЪрдпрди рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:

 type RedisConfig struct { RedisAddr string RedisPassword string RedisPrefix string } type Config struct { ... Storage string RedisConfig } func GetApi(config *Config) *martini.ClassicMartini { var storage Storage switch config.Storage{ case "redis": redisStorage := NewRedisStorage(config.RedisAddr, config.RedisPassword, config.RedisPassword, MAX_REQUEST_COUNT, BIN_LIFETIME) redisStorage.StartCleaning(60) storage = redisStorage default: memoryStorage := NewMemoryStorage(MAX_REQUEST_COUNT, BIN_LIFETIME) memoryStorage.StartCleaning(60) storage = memoryStorage } ... 

рд╣рдордиреЗ рдЕрдкрдиреА рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рд╕рдВрд░рдЪрдирд╛ рдореЗрдВ рдПрдХ рдирдпрд╛ рд╕рдВрдЧреНрд░рд╣рдг рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝рд╛ рдФрд░ рдЗрд╕рдХреЗ рдЖрдзрд╛рд░ рдкрд░, RedisStorage рдпрд╛ MemoryStorage рдХреЛ рдкреНрд░рд╛рд░рдВрдн рдХрд░рддреЗ рд╣реБрдПред рд╡рд┐рд╢рд┐рд╖реНрдЯ рдореВрд▓реА рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЗ рд▓рд┐рдП RedisConfig рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рднреА рдЬреЛрдбрд╝рд╛ред

рд╣рдо рд▓реЙрдиреНрдЪ рдХреА рдЬрд╛ рд░рд╣реА main.go рдлрд╛рдЗрд▓ рдореЗрдВ рднреА рдмрджрд▓рд╛рд╡ рдХрд░рддреЗ рд╣реИрдВ:
 import ( "skimmer" "flag" ) var ( config = skimmer.Config{ SessionSecret: "secret123", RedisConfig: skimmer.RedisConfig{ RedisAddr: "127.0.0.1:6379", RedisPassword: "", RedisPrefix: "skimmer", }, } ) func init() { flag.StringVar(&config.Storage, "storage", "memory", "available storages: redis, memory") flag.StringVar(&config.SessionSecret, "sessionSecret", config.SessionSecret, "") flag.StringVar(&config.RedisAddr, "redisAddr", config.RedisAddr, "redis storage only") flag.StringVar(&config.RedisPassword, "redisPassword", config.RedisPassword, "redis storage only") flag.StringVar(&config.RedisPrefix, "redisPrefix", config.RedisPrefix, "redis storage only") } func main() { flag.Parse() api := skimmer.GetApi(&config) api.Run() } 


рд╣рдо рдзреНрд╡рдЬ рдкреИрдХреЗрдЬ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ , рдЬреЛ рдХрд╛рд░реНрдпрдХреНрд░рдореЛрдВ рдХреЗ рд▓рд┐рдП рд▓реЙрдиреНрдЪ рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЛ рдЬреЛрдбрд╝рдирд╛ рдЖрд╕рд╛рди рдФрд░ рд╕рд░рд▓ рдмрдирд╛рддрд╛ рд╣реИред Init рдлрдВрдХреНрд╢рди рдореЗрдВ "рд╕реНрдЯреЛрд░реЗрдЬ" рдлреНрд▓реИрдЧ рдЬреЛрдбрд╝реЗрдВ, рдЬреЛ рд╕реНрдЯреЛрд░реЗрдЬ рдлреАрд▓реНрдб рдореЗрдВ рд╣рдорд╛рд░реЗ рдХреЙрдиреНрдлрд┐рдЧрд░ рдХреЛ рд╕реАрдзреЗ рд╡реИрд▓реНрдпреВ рд╕реЗрд╡ рдХрд░реЗрдЧрд╛ред рдореВрд▓реА рд▓реЙрдиреНрдЪ рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЛ рднреА рдЬреЛрдбрд╝реЗрдВред
рдЗрдирд┐рдЯ рдлрд╝рдВрдХреНрд╢рди рдЧреЛ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реЗрд╖ рд╣реИ; рдкреИрдХреЗрдЬ рд▓реЛрдб рд╣реЛрдиреЗ рдкрд░ рдЗрд╕реЗ рд╣рдореЗрд╢рд╛ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЧреЛ рдореЗрдВ рдЪрд▓ рд░рд╣реЗ рдХрд╛рд░реНрдпрдХреНрд░рдореЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдиреЗрдВ ред

рдЕрдм --рд╣реЗрд▓реНрдк рд╡рд┐рдХрд▓реНрдк рдХреЗ рд╕рд╛рде рдЕрдкрдирд╛ рдкреНрд░реЛрдЧреНрд░рд╛рдо рд╢реБрд░реВ рдХрд░рдХреЗ, рд╣рдо рдЙрдкрд▓рдмреНрдз рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреА рдПрдХ рд╕реВрдЪреА рджреЗрдЦреЗрдВрдЧреЗ:

 > go run ./src/main.go --help Usage of .../main: -redisAddr="127.0.0.1:6379": redis storage only -redisPassword="": redis storage only -redisPrefix="skimmer": redis storage only -sessionSecret="secret123": -storage="memory": available storages: redis, memory 


рдЕрдм рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдРрд╕рд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╣реИ рдЬреЛ рдЕрднреА рднреА рдХрд╛рдлреА рдХрдЪреНрдЪрд╛ рд╣реИ, рдФрд░ рдЕрдиреБрдХреВрд▓рд┐рдд рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рд░реНрд╡рд░ рдкрд░ рдХрд╛рдо рдХрд░рдиреЗ рдФрд░ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рд╣реИред

рддреАрд╕рд░реЗ рднрд╛рдЧ рдореЗрдВ, рд╣рдо рдЬреАрдПрдИ, рдХреЛрдХреЗрди рдФрд░ рд╣рд░реЛрдХреВ рдореЗрдВ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдмрд┐рдЫрд╛рдиреЗ рдФрд░ рд▓реЙрдиреНрдЪ рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗ, рд╕рд╛рде рд╣реА рд╕рднреА рд╕рдВрд╕рд╛рдзрдиреЛрдВ рд╕реЗ рдпреБрдХреНрдд рдПрдХ рдПрдХрд▓ рдирд┐рд╖реНрдкрд╛рджрди рдпреЛрдЧреНрдп рдлрд╝рд╛рдЗрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдЗрд╕реЗ рдХреИрд╕реЗ рд╡рд┐рддрд░рд┐рдд рдХрд░реЗрдВред рд╣рдо рдЕрдиреБрдХреВрд▓рди рдХрд░рддреЗ рд╕рдордп рдкреНрд░рджрд░реНрд╢рди рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦреЗрдВрдЧреЗред рд╣рдо рд╕реАрдЦрддреЗ рд╣реИрдВ рдХрд┐ рдХреИрд╕реЗ рдЖрд╡рд╢реНрдпрдХ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдЕрдиреБрд░реЛрдз рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХрд░рдирд╛ рд╣реИред рдЕрдВрдд рдореЗрдВ, рд╣рдо рд╡рд┐рддрд░рд┐рдд Groupcache рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рдЕрдВрджрд░ рдПрдореНрдмреЗрдб рдХрд░рддреЗ рд╣реИрдВред

рдореБрдЭреЗ рд▓реЗрдЦ рдХреЗ рдХрд┐рд╕реА рднреА рд╕реБрдзрд╛рд░ рдФрд░ рд╕реБрдЭрд╛рд╡ рдкрд░ рдЦреБрд╢реА рд╣реЛрдЧреАред

Source: https://habr.com/ru/post/In214425/


All Articles