Repository: gin-gonic/gin
Pull request #2739 (2021-05-28 06:11:01)

Setting trusted platform using an enum-like
Updated #2723 using an enum-like.

This also marks `AppEngine` as deprecated (note that we can't detect people using `AppEngine: false` because that's also the zero value, so we can only show an error message to people who set `AppEngine: true`).

This removes the `Cloudflare` property set in #2723, which was never released anyways.

codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#2739](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (b2e0a85) into [master](https://codecov.io/gh/gin-gonic/gin/commit/6703dea51c3a32d6b30f51ba0d6a7a7deae23e32?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (6703dea) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/2739/graphs/tree.svg?width=650&height=150&src=pr&token=Qpqxhwery8&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)

```diff
@@           Coverage Diff           @@
##           master    #2739   +/-   ##
=======================================
  Coverage   98.69%   98.70%           
=======================================
  Files          41       41           
  Lines        2074     2078    +4     
=======================================
+ Hits         2047     2051    +4     
  Misses         15       15           
  Partials       12       12           
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/2739/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Y29udGV4dC5nbw==) | `97.68% <100.00%> (+0.02%)` | :arrow_up: |
| [gin.go](https://codecov.io/gh/gin-gonic/gin/pull/2739/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Z2luLmdv) | `99.18% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Last update [6703dea...b2e0a85](https://codecov.io/gh/gin-gonic/gin/pull/2739?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index e86bc98..96e70bb 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,7 +1,7 @@
 - With pull requests:
   - Open your pull request against `master`
   - Your pull request should have no more than two commits, if not you should squash them.
-  - It should pass all tests in the available continuous integration systems such as TravisCI.
+  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
   - You should add/modify tests to cover your proposed code changes.
   - If your pull request contains a new feature, please document it on the README.
 
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
new file mode 100644
index 0000000..85cc560
--- /dev/null
+++ b/.github/workflows/gin.yml
@@ -0,0 +1,62 @@
+name: Run Tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  test:
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        go: [1.13, 1.14, 1.15, 1.16]
+        test-tags: ['', nomsgpack]
+    name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
+    runs-on: ${{ matrix.os }}
+    env:
+      GO111MODULE: on
+      TESTTAGS: ${{ matrix.test-tags }}
+      GOPROXY: https://proxy.golang.org
+    steps:
+      - name: Set up Go ${{ matrix.go }}
+        uses: actions/setup-go@v2
+        with:
+          go-version: ${{ matrix.go }}
+
+      - name: Checkout Code
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.ref }}
+
+      - name: Install Dependencies
+        run: make tools
+
+      - name: Run Check
+        run: |
+          make vet
+          make fmt-check
+          make misspell-check
+
+      - name: Run Tests
+        run: make test
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v1
+  notification-gitter:
+    needs: test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Notification failure message
+        if: failure()
+        run: |
+          PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
+          curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4
+      - name: Notification success message
+        if: success()
+        run: |
+          PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
+          curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8166231..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-language: go
-
-matrix:
-  fast_finish: true
-  include:
-  - go: 1.13.x
-  - go: 1.13.x
-    env:
-      - TESTTAGS=nomsgpack
-  - go: 1.14.x
-  - go: 1.14.x
-    env:
-      - TESTTAGS=nomsgpack
-  - go: 1.15.x
-  - go: 1.15.x
-    env:
-      - TESTTAGS=nomsgpack
-  - go: 1.16.x
-  - go: 1.16.x
-    env:
-      - TESTTAGS=nomsgpack
-  - go: master
-
-git:
-  depth: 10
-
-before_install:
-  - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
-
-install:
-  - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
-  - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
-  - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
-
-go_import_path: github.com/gin-gonic/gin
-
-script:
-  - make vet
-  - make fmt-check
-  - make misspell-check
-  - make test
-
-after_success:
-  - bash <(curl -s https://codecov.io/bash)
-
-notifications:
-  webhooks:
-    urls:
-      - https://webhooks.gitter.im/e/7f95bf605c4d356372f4
-    on_success: change  # options: [always|never|change] default: always
-    on_failure: always  # options: [always|never|change] default: always
-    on_start: false     # default: false
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 97daa80..d1c723c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,6 +8,6 @@
 - With pull requests:
   - Open your pull request against `master`
   - Your pull request should have no more than two commits, if not you should squash them.
-  - It should pass all tests in the available continuous integration systems such as TravisCI.
+  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
   - You should add/modify tests to cover your proposed code changes.
   - If your pull request contains a new feature, please document it on the README.
diff --git a/Makefile b/Makefile
index 1a99193..5d55b44 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
 GO ?= go
 GOFMT ?= gofmt "-s"
+GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
 PACKAGES ?= $(shell $(GO) list ./...)
 VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
 GOFILES := $(shell find . -name "*.go")
@@ -67,5 +68,10 @@ misspell:
 
 .PHONY: tools
 tools:
-	go install golang.org/x/lint/golint; \
-	go install github.com/client9/misspell/cmd/misspell;
+	@if [ $(GO_VERSION) -gt 15 ]; then \
+		$(GO) install golang.org/x/lint/golint@latest; \
+		$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
+	elif [ $(GO_VERSION) -lt 16 ]; then \
+		$(GO) install golang.org/x/lint/golint; \
+		$(GO) install github.com/client9/misspell/cmd/misspell; \
+	fi
diff --git a/README.md b/README.md
index 38c6748..ef37fd2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 <img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
 
-[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
+[![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster)
 [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
 [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
 [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
diff --git a/binding/form.go b/binding/form.go
index b93c34c..040af9e 100644
--- a/binding/form.go
+++ b/binding/form.go
@@ -22,10 +22,8 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
 	if err := req.ParseForm(); err != nil {
 		return err
 	}
-	if err := req.ParseMultipartForm(defaultMemory); err != nil {
-		if err != http.ErrNotMultipart {
-			return err
-		}
+	if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart {
+		return err
 	}
 	if err := mapForm(obj, req.Form); err != nil {
 		return err
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index 2f4e45b..421c0f7 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -16,7 +16,15 @@ import (
 	"github.com/gin-gonic/gin/internal/json"
 )
 
-var errUnknownType = errors.New("unknown type")
+var (
+	errUnknownType = errors.New("unknown type")
+
+	// ErrConvertMapStringSlice can not covert to map[string][]string
+	ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
+
+	// ErrConvertToMapString can not convert to map[string]string
+	ErrConvertToMapString = errors.New("can not convert to map of strings")
+)
 
 func mapUri(ptr interface{}, m map[string][]string) error {
 	return mapFormByTag(ptr, m, "uri")
@@ -109,7 +117,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 			if sf.PkgPath != "" && !sf.Anonymous { // unexported
 				continue
 			}
-			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
+			ok, err := mapping(value.Field(i), sf, setter, tag)
 			if err != nil {
 				return false, err
 			}
@@ -371,7 +379,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
 	if el.Kind() == reflect.Slice {
 		ptrMap, ok := ptr.(map[string][]string)
 		if !ok {
-			return errors.New("cannot convert to map slices of strings")
+			return ErrConvertMapStringSlice
 		}
 		for k, v := range form {
 			ptrMap[k] = v
@@ -382,7 +390,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
 
 	ptrMap, ok := ptr.(map[string]string)
 	if !ok {
-		return errors.New("cannot convert to map of strings")
+		return ErrConvertToMapString
 	}
 	for k, v := range form {
 		ptrMap[k] = v[len(v)-1] // pick last
diff --git a/binding/header.go b/binding/header.go
index 179ce4e..b99302a 100644
--- a/binding/header.go
+++ b/binding/header.go
@@ -29,6 +29,6 @@ type headerSource map[string][]string
 
 var _ setter = headerSource(nil)
 
-func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {
 	return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
 }
diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go
index f85a1aa..69c0a54 100644
--- a/binding/multipart_form_mapping.go
+++ b/binding/multipart_form_mapping.go
@@ -15,8 +15,16 @@ type multipartRequest http.Request
 
 var _ setter = (*multipartRequest)(nil)
 
+var (
+	// ErrMultiFileHeader multipart.FileHeader invalid
+	ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader")
+
+	// ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid
+	ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader")
+)
+
 // TrySet tries to set a value by the multipart request with the binding a form file
-func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
+func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {
 	if files := r.MultipartForm.File[key]; len(files) != 0 {
 		return setByMultipartFormFile(value, field, files)
 	}
@@ -49,12 +57,12 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
 	case reflect.Array:
 		return setArrayOfMultipartFormFiles(value, field, files)
 	}
-	return false, errors.New("unsupported field type for multipart.FileHeader")
+	return false, ErrMultiFileHeader
 }
 
 func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
 	if value.Len() != len(files) {
-		return false, errors.New("unsupported len of array for []*multipart.FileHeader")
+		return false, ErrMultiFileHeaderLenInvalid
 	}
 	for i := range files {
 		setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
diff --git a/context.go b/context.go
index 0c1fb07..5c1aba5 100644
--- a/context.go
+++ b/context.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"log"
 	"math"
 	"mime/multipart"
 	"net"
@@ -731,17 +732,26 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
 // If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
 // the remote IP (coming form Request.RemoteAddr) is returned.
 func (c *Context) ClientIP() string {
-	switch {
-	case c.engine.AppEngine:
+	// Check if we're running on a tursted platform
+	switch c.engine.TrustedPlatform {
+	case PlatformGoogleAppEngine:
 		if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
 			return addr
 		}
-	case c.engine.CloudflareProxy:
+	case PlatformCloudflare:
 		if addr := c.requestHeader("CF-Connecting-IP"); addr != "" {
 			return addr
 		}
 	}
 
+	// Legacy "AppEngine" flag
+	if c.engine.AppEngine {
+		log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`)
+		if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
+			return addr
+		}
+	}
+
 	remoteIP, trusted := c.RemoteIP()
 	if remoteIP == nil {
 		return ""
diff --git a/context_appengine.go b/context_appengine.go
index d565843..8bf9389 100644
--- a/context_appengine.go
+++ b/context_appengine.go
@@ -8,5 +8,5 @@
 package gin
 
 func init() {
-	defaultAppEngine = true
+	defaultPlatform = PlatformGoogleAppEngine
 }
diff --git a/debug.go b/debug.go
index 9bacc68..ed31386 100644
--- a/debug.go
+++ b/debug.go
@@ -95,9 +95,7 @@ at initialization. ie. before any route is registered or the router is listening
 }
 
 func debugPrintError(err error) {
-	if err != nil {
-		if IsDebugging() {
-			fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
-		}
+	if err != nil && IsDebugging() {
+		fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
 	}
 }
diff --git a/gin.go b/gin.go
index 00686e7..fd4fa51 100644
--- a/gin.go
+++ b/gin.go
@@ -25,7 +25,7 @@ var (
 	default405Body = []byte("405 method not allowed")
 )
 
-var defaultAppEngine bool
+var defaultPlatform string
 
 // HandlerFunc defines the handler used by gin middleware as return value.
 type HandlerFunc func(*Context)
@@ -52,6 +52,16 @@ type RouteInfo struct {
 // RoutesInfo defines a RouteInfo array.
 type RoutesInfo []RouteInfo
 
+// Trusted platforms
+const (
+	// When running on Google App Engine. Trust X-Appengine-Remote-Addr
+	// for determining the client's IP
+	PlatformGoogleAppEngine = "google-app-engine"
+	// When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
+	// the client's IP
+	PlatformCloudflare = "cloudflare"
+)
+
 // Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
 // Create an instance of Engine, by using New() or Default()
 type Engine struct {
@@ -101,14 +111,15 @@ type Engine struct {
 	// `true`.
 	TrustedProxies []string
 
+	// If set to a constant of value gin.Platform*, trusts the headers set by
+	// that platform, for example to determine the client IP
+	TrustedPlatform string
+
+	// DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD
 	// #726 #755 If enabled, it will trust some headers starting with
 	// 'X-AppEngine...' for better integration with that PaaS.
 	AppEngine bool
 
-	// If enabled, it will trust the CF-Connecting-IP header to determine the
-	// IP of the client.
-	CloudflareProxy bool
-
 	// If enabled, the url.RawPath will be used to find parameters.
 	UseRawPath bool
 
@@ -164,7 +175,7 @@ func New() *Engine {
 		ForwardedByClientIP:    true,
 		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
 		TrustedProxies:         []string{"0.0.0.0/0"},
-		AppEngine:              defaultAppEngine,
+		TrustedPlatform:        defaultPlatform,
 		UseRawPath:             false,
 		RemoveExtraSlash:       false,
 		UnescapePathValues:     true,
diff --git a/render/json.go b/render/json.go
index 4186309..e25415b 100644
--- a/render/json.go
+++ b/render/json.go
@@ -46,9 +46,11 @@ type PureJSON struct {
 	Data interface{}
 }
 
-var jsonContentType = []string{"application/json; charset=utf-8"}
-var jsonpContentType = []string{"application/javascript; charset=utf-8"}
-var jsonAsciiContentType = []string{"application/json"}
+var (
+	jsonContentType      = []string{"application/json; charset=utf-8"}
+	jsonpContentType     = []string{"application/javascript; charset=utf-8"}
+	jsonAsciiContentType = []string{"application/json"}
+)
 
 // Render (JSON) writes data with custom ContentType.
 func (r JSON) Render(w http.ResponseWriter) (err error) {
diff --git a/tree.go b/tree.go
index 0d082d0..5f35fff 100644
--- a/tree.go
+++ b/tree.go
@@ -30,8 +30,8 @@ type Param struct {
 // It is therefore safe to read values by the index.
 type Params []Param
 
-// Get returns the value of the first Param which key matches the given name.
-// If no matching Param is found, an empty string is returned.
+// Get returns the value of the first Param which key matches the given name and a boolean true.
+// If no matching Param is found, an empty string is returned and a boolean false .
 func (ps Params) Get(name string) (string, bool) {
 	for _, entry := range ps {
 		if entry.Key == name {
@@ -487,7 +487,7 @@ walk: // Outer loop for walking the tree
 						}
 
 						// ... but we can't
-						value.tsr = (len(path) == end+1)
+						value.tsr = len(path) == end+1
 						return
 					}
 
@@ -499,7 +499,7 @@ walk: // Outer loop for walking the tree
 						// No handle found. Check if a handle for this path + a
 						// trailing slash exists for TSR recommendation
 						n = n.children[0]
-						value.tsr = (n.path == "/" && n.handlers != nil)
+						value.tsr = n.path == "/" && n.handlers != nil
 					}
 					return
 


Test patch:
diff --git a/context_test.go b/context_test.go
index e0de717..93b58ee 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1410,7 +1410,7 @@ func TestContextClientIP(t *testing.T) {
 
 	c.Request.Header.Del("X-Forwarded-For")
 	c.Request.Header.Del("X-Real-IP")
-	c.engine.AppEngine = true
+	c.engine.TrustedPlatform = PlatformGoogleAppEngine
 	assert.Equal(t, "50.50.50.50", c.ClientIP())
 
 	c.Request.Header.Del("X-Appengine-Remote-Addr")
@@ -1470,19 +1470,27 @@ func TestContextClientIP(t *testing.T) {
 	assert.Equal(t, "10.10.10.10", c.ClientIP())
 
 	c.engine.RemoteIPHeaders = []string{}
+	c.engine.TrustedPlatform = PlatformGoogleAppEngine
+	assert.Equal(t, "50.50.50.50", c.ClientIP())
+
+	// Test the legacy flag
+	c.engine.TrustedPlatform = ""
 	c.engine.AppEngine = true
 	assert.Equal(t, "50.50.50.50", c.ClientIP())
+	c.engine.AppEngine = false
+	c.engine.TrustedPlatform = PlatformGoogleAppEngine
 
 	c.Request.Header.Del("X-Appengine-Remote-Addr")
 	assert.Equal(t, "40.40.40.40", c.ClientIP())
 
-	c.engine.AppEngine = false
-	c.engine.CloudflareProxy = true
+	c.engine.TrustedPlatform = PlatformCloudflare
 	assert.Equal(t, "60.60.60.60", c.ClientIP())
 
 	c.Request.Header.Del("CF-Connecting-IP")
 	assert.Equal(t, "40.40.40.40", c.ClientIP())
 
+	c.engine.TrustedPlatform = ""
+
 	// no port
 	c.Request.RemoteAddr = "50.50.50.50"
 	assert.Empty(t, c.ClientIP())
@@ -1494,6 +1502,7 @@ func resetContextForClientIPTests(c *Context) {
 	c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
 	c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
 	c.Request.RemoteAddr = "  40.40.40.40:42123 "
+	c.engine.TrustedPlatform = ""
 	c.engine.AppEngine = false
 }