Repository: labstack/echo
Pull request #1277 (2019-02-08 12:54:29)

Echo.StartTLSFromFile: Add function that loads key pair from strings
`echo.go` - `Echo. StartTLSFromFile`:
* Add function that loads key pair from strings.

`echo_test.go` - `Echo. StartTLSFromFile`:
* Add test `TestEchoStartTLSFromFile`.

codecov[bot]: # [Codecov](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=h1) Report
> Merging [#1277](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=desc) into [master](https://codecov.io/gh/labstack/echo/commit/88965757af989d8a23419d192b418670e7cba67c?src=pr&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `75%`.

[![Impacted file tree graph](https://codecov.io/gh/labstack/echo/pull/1277/graphs/tree.svg?width=650&token=vqnT76l79i&height=150&src=pr)](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1277      +/-   ##
==========================================
+ Coverage   81.78%   81.83%   +0.04%     
==========================================
  Files          26       26              
  Lines        1933     1938       +5     
==========================================
+ Hits         1581     1586       +5     
  Misses        243      243              
  Partials      109      109
```


| [Impacted Files](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [echo.go](https://codecov.io/gh/labstack/echo/pull/1277/diff?src=pr&el=tree#diff-ZWNoby5nbw==) | `87.18% <75%> (+0.23%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=footer). Last update [8896575...cb3fa28](https://codecov.io/gh/labstack/echo/pull/1277?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

mbana: > small fix, see above

See latest PR, I did a force push (sorry).
Thanks
alexaandru: Looks good! :) 

Patch:
diff --git a/context.go b/context.go
index d472270..75f59fd 100644
--- a/context.go
+++ b/context.go
@@ -13,6 +13,7 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"sync"
 )
 
 type (
@@ -198,6 +199,7 @@ type (
 		handler  HandlerFunc
 		store    Map
 		echo     *Echo
+		lock     sync.RWMutex
 	}
 )
 
@@ -360,10 +362,15 @@ func (c *context) Cookies() []*http.Cookie {
 }
 
 func (c *context) Get(key string) interface{} {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
 	return c.store[key]
 }
 
 func (c *context) Set(key string, val interface{}) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
 	if c.store == nil {
 		c.store = make(Map)
 	}
@@ -597,4 +604,3 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
 	// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
 	// c.pvalues = nil
 }
-
diff --git a/echo.go b/echo.go
index 6a41e56..4f8209b 100644
--- a/echo.go
+++ b/echo.go
@@ -43,6 +43,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	stdLog "log"
 	"net"
 	"net/http"
@@ -269,6 +270,7 @@ var (
 	ErrRendererNotRegistered       = errors.New("renderer not registered")
 	ErrInvalidRedirectCode         = errors.New("invalid redirect status code")
 	ErrCookieNotFound              = errors.New("cookie not found")
+	ErrInvalidCertOrKeyType        = errors.New("invalid cert or key type, must be string or []byte")
 )
 
 // Error handlers
@@ -605,20 +607,40 @@ func (e *Echo) Start(address string) error {
 }
 
 // StartTLS starts an HTTPS server.
-func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
-	if certFile == "" || keyFile == "" {
-		return errors.New("invalid tls configuration")
+// If `certFile` or `keyFile` is `string` the values are treated as file paths.
+// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
+func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
+	var cert []byte
+	if cert, err = filepathOrContent(certFile); err != nil {
+		return
 	}
+
+	var key []byte
+	if key, err = filepathOrContent(keyFile); err != nil {
+		return
+	}
+
 	s := e.TLSServer
 	s.TLSConfig = new(tls.Config)
 	s.TLSConfig.Certificates = make([]tls.Certificate, 1)
-	s.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
-	if err != nil {
+	if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
 		return
 	}
+
 	return e.startTLS(address)
 }
 
+func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
+	switch v := fileOrContent.(type) {
+	case string:
+		return ioutil.ReadFile(v)
+	case []byte:
+		return v, nil
+	default:
+		return nil, ErrInvalidCertOrKeyType
+	}
+}
+
 // StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
 func (e *Echo) StartAutoTLS(address string) error {
 	s := e.TLSServer


Test patch:
diff --git a/context_test.go b/context_test.go
index 3a47364..6118866 100644
--- a/context_test.go
+++ b/context_test.go
@@ -529,6 +529,21 @@ func TestContextStore(t *testing.T) {
 	testify.Equal(t, "Jon Snow", c.Get("name"))
 }
 
+func BenchmarkContext_Store(b *testing.B) {
+	e := &Echo{}
+
+	c := &context{
+		echo: e,
+	}
+
+	for n := 0; n < b.N; n++ {
+		c.Set("name", "Jon Snow")
+		if c.Get("name") != "Jon Snow" {
+			b.Fail()
+		}
+	}
+}
+
 func TestContextHandler(t *testing.T) {
 	e := New()
 	r := e.Router()
@@ -543,4 +558,3 @@ func TestContextHandler(t *testing.T) {
 	c.Handler()(c)
 	testify.Equal(t, "handler", b.String())
 }
-
diff --git a/echo_test.go b/echo_test.go
index 3ac10a7..dec713e 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	stdContext "context"
 	"errors"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
@@ -12,6 +13,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 type (
@@ -428,6 +430,71 @@ func TestEchoStartTLS(t *testing.T) {
 	e.Close()
 }
 
+func TestEchoStartTLSByteString(t *testing.T) {
+	cert, err := ioutil.ReadFile("_fixture/certs/cert.pem")
+	require.NoError(t, err)
+	key, err := ioutil.ReadFile("_fixture/certs/key.pem")
+	require.NoError(t, err)
+
+	testCases := []struct {
+		cert        interface{}
+		key         interface{}
+		expectedErr error
+		name        string
+	}{
+		{
+			cert:        "_fixture/certs/cert.pem",
+			key:         "_fixture/certs/key.pem",
+			expectedErr: nil,
+			name:        `ValidCertAndKeyFilePath`,
+		},
+		{
+			cert:        cert,
+			key:         key,
+			expectedErr: nil,
+			name:        `ValidCertAndKeyByteString`,
+		},
+		{
+			cert:        cert,
+			key:         1,
+			expectedErr: ErrInvalidCertOrKeyType,
+			name:        `InvalidKeyType`,
+		},
+		{
+			cert:        0,
+			key:         key,
+			expectedErr: ErrInvalidCertOrKeyType,
+			name:        `InvalidCertType`,
+		},
+		{
+			cert:        0,
+			key:         1,
+			expectedErr: ErrInvalidCertOrKeyType,
+			name:        `InvalidCertAndKeyTypes`,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			e := New()
+			e.HideBanner = true
+
+			go func() {
+				err := e.StartTLS(":0", test.cert, test.key)
+				if test.expectedErr != nil {
+					require.EqualError(t, err, test.expectedErr.Error())
+				} else if err != http.ErrServerClosed { // Prevent the test to fail after closing the servers
+					require.NoError(t, err)
+				}
+			}()
+			time.Sleep(200 * time.Millisecond)
+
+			require.NoError(t, e.Close())
+		})
+	}
+}
+
 func TestEchoStartAutoTLS(t *testing.T) {
 	e := New()
 	errChan := make(chan error, 0)