wa-lang.org/wazero@v1.0.2/imports/proxywasm/_proxytest/http.go (about)

     1  // Copyright 2020-2021 Tetrate
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proxytest
    16  
    17  import (
    18  	"log"
    19  	"strings"
    20  
    21  	"wa-lang.org/wazero/imports/proxywasm/internal"
    22  	"wa-lang.org/wazero/imports/proxywasm/types"
    23  )
    24  
    25  type (
    26  	httpHostEmulator struct {
    27  		httpStreams map[uint32]*httpStreamState
    28  	}
    29  	httpStreamState struct {
    30  		requestHeaders, responseHeaders   [][2]string
    31  		requestTrailers, responseTrailers [][2]string
    32  
    33  		// bodyBuffer keeps the body read so far when plugins request buffering
    34  		// by returning types.ActionPause. Buffers are cleared when types.ActionContinue
    35  		// is returned.
    36  		requestBodyBuffer, responseBodyBuffer []byte
    37  		// body is the body visible to the plugin, which will include anything
    38  		// in its bodyBuffer as well. If types.ActionContinue is returned, the
    39  		// content of body is sent to the upstream or downstream.
    40  		requestBody, responseBody []byte
    41  
    42  		action            types.Action
    43  		sentLocalResponse *LocalHttpResponse
    44  	}
    45  	LocalHttpResponse struct {
    46  		StatusCode       uint32
    47  		StatusCodeDetail string
    48  		Data             []byte
    49  		Headers          [][2]string
    50  		GRPCStatus       int32
    51  	}
    52  )
    53  
    54  func newHttpHostEmulator() *httpHostEmulator {
    55  	host := &httpHostEmulator{httpStreams: map[uint32]*httpStreamState{}}
    56  	return host
    57  }
    58  
    59  // impl internal.ProxyWasmHost: delegated from hostEmulator
    60  func (h *httpHostEmulator) httpHostEmulatorProxyGetBufferBytes(bt internal.BufferType, start int, maxSize int,
    61  	returnBufferData **byte, returnBufferSize *int) internal.Status {
    62  	active := internal.VMStateGetActiveContextID()
    63  	stream := h.httpStreams[active]
    64  	var buf []byte
    65  	switch bt {
    66  	case internal.BufferTypeHttpRequestBody:
    67  		buf = stream.requestBody
    68  	case internal.BufferTypeHttpResponseBody:
    69  		buf = stream.responseBody
    70  	default:
    71  		panic("unreachable: maybe a bug in this host emulation or SDK")
    72  	}
    73  
    74  	if len(buf) == 0 {
    75  		return internal.StatusNotFound
    76  	} else if start >= len(buf) {
    77  		log.Printf("start index out of range: %d (start) >= %d ", start, len(buf))
    78  		return internal.StatusBadArgument
    79  	}
    80  
    81  	*returnBufferData = &buf[start]
    82  	if maxSize > len(buf)-start {
    83  		*returnBufferSize = len(buf) - start
    84  	} else {
    85  		*returnBufferSize = maxSize
    86  	}
    87  	return internal.StatusOK
    88  }
    89  
    90  func (h *httpHostEmulator) httpHostEmulatorProxySetBufferBytes(bt internal.BufferType, start int, maxSize int,
    91  	bufferData *byte, bufferSize int) internal.Status {
    92  	active := internal.VMStateGetActiveContextID()
    93  	stream := h.httpStreams[active]
    94  	var targetBuf *[]byte
    95  	switch bt {
    96  	case internal.BufferTypeHttpRequestBody:
    97  		targetBuf = &stream.requestBody
    98  	case internal.BufferTypeHttpResponseBody:
    99  		targetBuf = &stream.responseBody
   100  	default:
   101  		panic("unreachable: maybe a bug in this host emulation or SDK")
   102  	}
   103  
   104  	body := internal.RawBytePtrToByteSlice(bufferData, bufferSize)
   105  	if start == 0 {
   106  		if maxSize == 0 {
   107  			// Prepend
   108  			*targetBuf = append(body, *targetBuf...)
   109  			return internal.StatusOK
   110  		} else if maxSize >= len(*targetBuf) {
   111  			// Replace
   112  			*targetBuf = body
   113  			return internal.StatusOK
   114  		} else {
   115  			return internal.StatusBadArgument
   116  		}
   117  	} else if start >= len(*targetBuf) {
   118  		// Append.
   119  		*targetBuf = append(*targetBuf, body...)
   120  		return internal.StatusOK
   121  	} else {
   122  		return internal.StatusBadArgument
   123  	}
   124  }
   125  
   126  // impl internal.ProxyWasmHost: delegated from hostEmulator
   127  func (h *httpHostEmulator) httpHostEmulatorProxyGetHeaderMapValue(mapType internal.MapType, keyData *byte,
   128  	keySize int, returnValueData **byte, returnValueSize *int) internal.Status {
   129  	active := internal.VMStateGetActiveContextID()
   130  	stream := h.httpStreams[active]
   131  
   132  	var headers [][2]string
   133  	switch mapType {
   134  	case internal.MapTypeHttpRequestHeaders:
   135  		headers = stream.requestHeaders
   136  	case internal.MapTypeHttpResponseHeaders:
   137  		headers = stream.responseHeaders
   138  	case internal.MapTypeHttpRequestTrailers:
   139  		headers = stream.requestTrailers
   140  	case internal.MapTypeHttpResponseTrailers:
   141  		headers = stream.responseTrailers
   142  	default:
   143  		panic("unreachable: maybe a bug in this host emulation or SDK")
   144  	}
   145  
   146  	key := strings.ToLower(internal.RawBytePtrToString(keyData, keySize))
   147  
   148  	for _, h := range headers {
   149  		if h[0] == key {
   150  			// Leading LWS doesn't affect header values,
   151  			// and often ignored in HTTP parsers.
   152  			value := []byte(strings.TrimSpace(h[1]))
   153  			// If the value is empty,
   154  			// Envoy ignores such headers and return NotFound.
   155  			if len(value) == 0 {
   156  				return internal.StatusNotFound
   157  			}
   158  			*returnValueData = &value[0]
   159  			*returnValueSize = len(value)
   160  			return internal.StatusOK
   161  		}
   162  	}
   163  
   164  	return internal.StatusNotFound
   165  }
   166  
   167  // impl internal.ProxyWasmHost
   168  func (h *httpHostEmulator) ProxyAddHeaderMapValue(mapType internal.MapType, keyData *byte,
   169  	keySize int, valueData *byte, valueSize int) internal.Status {
   170  
   171  	key := internal.RawBytePtrToString(keyData, keySize)
   172  	value := internal.RawBytePtrToString(valueData, valueSize)
   173  	active := internal.VMStateGetActiveContextID()
   174  	stream := h.httpStreams[active]
   175  
   176  	switch mapType {
   177  	case internal.MapTypeHttpRequestHeaders:
   178  		stream.requestHeaders = addMapValue(stream.requestHeaders, key, value)
   179  	case internal.MapTypeHttpResponseHeaders:
   180  		stream.responseHeaders = addMapValue(stream.responseHeaders, key, value)
   181  	case internal.MapTypeHttpRequestTrailers:
   182  		stream.requestTrailers = addMapValue(stream.requestTrailers, key, value)
   183  	case internal.MapTypeHttpResponseTrailers:
   184  		stream.responseTrailers = addMapValue(stream.responseTrailers, key, value)
   185  	default:
   186  		panic("unimplemented")
   187  	}
   188  
   189  	return internal.StatusOK
   190  }
   191  
   192  func addMapValue(base [][2]string, key, value string) [][2]string {
   193  	key = strings.ToLower(key)
   194  	for i, h := range base {
   195  		if h[0] == key {
   196  			h[1] += value
   197  			base[i] = h
   198  			return base
   199  		}
   200  	}
   201  	return append(base, [2]string{key, value})
   202  }
   203  
   204  // impl internal.ProxyWasmHost
   205  func (h *httpHostEmulator) ProxyReplaceHeaderMapValue(mapType internal.MapType, keyData *byte,
   206  	keySize int, valueData *byte, valueSize int) internal.Status {
   207  	key := internal.RawBytePtrToString(keyData, keySize)
   208  	value := internal.RawBytePtrToString(valueData, valueSize)
   209  	active := internal.VMStateGetActiveContextID()
   210  	stream := h.httpStreams[active]
   211  
   212  	switch mapType {
   213  	case internal.MapTypeHttpRequestHeaders:
   214  		stream.requestHeaders = replaceMapValue(stream.requestHeaders, key, value)
   215  	case internal.MapTypeHttpResponseHeaders:
   216  		stream.responseHeaders = replaceMapValue(stream.responseHeaders, key, value)
   217  	case internal.MapTypeHttpRequestTrailers:
   218  		stream.requestTrailers = replaceMapValue(stream.requestTrailers, key, value)
   219  	case internal.MapTypeHttpResponseTrailers:
   220  		stream.responseTrailers = replaceMapValue(stream.responseTrailers, key, value)
   221  	default:
   222  		panic("unimplemented")
   223  	}
   224  	return internal.StatusOK
   225  }
   226  
   227  // impl internal.ProxyWasmHost
   228  func replaceMapValue(base [][2]string, key, value string) [][2]string {
   229  	key = strings.ToLower(key)
   230  	for i, h := range base {
   231  		if h[0] == key {
   232  			h[1] = value
   233  			base[i] = h
   234  			return base
   235  		}
   236  	}
   237  	return append(base, [2]string{key, value})
   238  }
   239  
   240  // impl internal.ProxyWasmHost
   241  func (h *httpHostEmulator) ProxyRemoveHeaderMapValue(mapType internal.MapType, keyData *byte, keySize int) internal.Status {
   242  	key := internal.RawBytePtrToString(keyData, keySize)
   243  	active := internal.VMStateGetActiveContextID()
   244  	stream := h.httpStreams[active]
   245  
   246  	switch mapType {
   247  	case internal.MapTypeHttpRequestHeaders:
   248  		stream.requestHeaders = removeHeaderMapValue(stream.requestHeaders, key)
   249  	case internal.MapTypeHttpResponseHeaders:
   250  		stream.responseHeaders = removeHeaderMapValue(stream.responseHeaders, key)
   251  	case internal.MapTypeHttpRequestTrailers:
   252  		stream.requestTrailers = removeHeaderMapValue(stream.requestTrailers, key)
   253  	case internal.MapTypeHttpResponseTrailers:
   254  		stream.responseTrailers = removeHeaderMapValue(stream.responseTrailers, key)
   255  	default:
   256  		panic("unimplemented")
   257  	}
   258  	return internal.StatusOK
   259  }
   260  
   261  func removeHeaderMapValue(base [][2]string, key string) [][2]string {
   262  	key = strings.ToLower(key)
   263  	for i, h := range base {
   264  		if h[0] == key {
   265  			if len(base)-1 == i {
   266  				return base[:i]
   267  			} else {
   268  				return append(base[:i], base[i+1:]...)
   269  			}
   270  		}
   271  	}
   272  	return base
   273  }
   274  
   275  // impl internal.ProxyWasmHost: delegated from hostEmulator
   276  func (h *httpHostEmulator) httpHostEmulatorProxyGetHeaderMapPairs(mapType internal.MapType, returnValueData **byte,
   277  	returnValueSize *int) internal.Status {
   278  	active := internal.VMStateGetActiveContextID()
   279  	stream := h.httpStreams[active]
   280  
   281  	var m []byte
   282  	switch mapType {
   283  	case internal.MapTypeHttpRequestHeaders:
   284  		m = internal.SerializeMap(stream.requestHeaders)
   285  	case internal.MapTypeHttpResponseHeaders:
   286  		m = internal.SerializeMap(stream.responseHeaders)
   287  	case internal.MapTypeHttpRequestTrailers:
   288  		m = internal.SerializeMap(stream.requestTrailers)
   289  	case internal.MapTypeHttpResponseTrailers:
   290  		m = internal.SerializeMap(stream.responseTrailers)
   291  	default:
   292  		panic("unreachable: maybe a bug in this host emulation or SDK")
   293  	}
   294  
   295  	if len(m) == 0 {
   296  		// The host might reutrn OK without setting the data pointer,
   297  		// if there's nothing to pass to Wasm VM.
   298  		*returnValueData = nil
   299  		*returnValueSize = 0
   300  		return internal.StatusOK
   301  	}
   302  
   303  	*returnValueData = &m[0]
   304  	*returnValueSize = len(m)
   305  	return internal.StatusOK
   306  }
   307  
   308  // impl internal.ProxyWasmHost
   309  func (h *httpHostEmulator) ProxySetHeaderMapPairs(mapType internal.MapType, mapData *byte, mapSize int) internal.Status {
   310  	m := deserializeRawBytePtrToMap(mapData, mapSize)
   311  	active := internal.VMStateGetActiveContextID()
   312  	stream := h.httpStreams[active]
   313  
   314  	switch mapType {
   315  	case internal.MapTypeHttpRequestHeaders:
   316  		stream.requestHeaders = m
   317  	case internal.MapTypeHttpResponseHeaders:
   318  		stream.responseHeaders = m
   319  	case internal.MapTypeHttpRequestTrailers:
   320  		stream.requestTrailers = m
   321  	case internal.MapTypeHttpResponseTrailers:
   322  		stream.responseTrailers = m
   323  	default:
   324  		panic("unimplemented")
   325  	}
   326  	return internal.StatusOK
   327  }
   328  
   329  // impl internal.ProxyWasmHost
   330  func (h *httpHostEmulator) ProxyContinueStream(internal.StreamType) internal.Status {
   331  	active := internal.VMStateGetActiveContextID()
   332  	stream := h.httpStreams[active]
   333  	stream.action = types.ActionContinue
   334  	return internal.StatusOK
   335  }
   336  
   337  // impl internal.ProxyWasmHost
   338  func (h *httpHostEmulator) ProxySendLocalResponse(statusCode uint32,
   339  	statusCodeDetailData *byte, statusCodeDetailsSize int, bodyData *byte, bodySize int,
   340  	headersData *byte, headersSize int, grpcStatus int32) internal.Status {
   341  	active := internal.VMStateGetActiveContextID()
   342  	stream := h.httpStreams[active]
   343  	stream.sentLocalResponse = &LocalHttpResponse{
   344  		StatusCode:       statusCode,
   345  		StatusCodeDetail: internal.RawBytePtrToString(statusCodeDetailData, statusCodeDetailsSize),
   346  		Data:             internal.RawBytePtrToByteSlice(bodyData, bodySize),
   347  		Headers:          deserializeRawBytePtrToMap(headersData, headersSize),
   348  		GRPCStatus:       grpcStatus,
   349  	}
   350  	return internal.StatusOK
   351  }
   352  
   353  // impl HostEmulator
   354  func (h *httpHostEmulator) InitializeHttpContext() (contextID uint32) {
   355  	contextID = getNextContextID()
   356  	internal.ProxyOnContextCreate(contextID, PluginContextID)
   357  	h.httpStreams[contextID] = &httpStreamState{action: types.ActionContinue}
   358  	return
   359  }
   360  
   361  // impl HostEmulator
   362  func (h *httpHostEmulator) CallOnRequestHeaders(contextID uint32, headers [][2]string, endOfStream bool) types.Action {
   363  	cs, ok := h.httpStreams[contextID]
   364  	if !ok {
   365  		log.Fatalf("invalid context id: %d", contextID)
   366  	}
   367  
   368  	cs.requestHeaders = cloneWithLowerCaseMapKeys(headers)
   369  	cs.action = internal.ProxyOnRequestHeaders(contextID,
   370  		len(headers), endOfStream)
   371  	return cs.action
   372  }
   373  
   374  // impl HostEmulator
   375  func (h *httpHostEmulator) CallOnResponseHeaders(contextID uint32, headers [][2]string, endOfStream bool) types.Action {
   376  	cs, ok := h.httpStreams[contextID]
   377  	if !ok {
   378  		log.Fatalf("invalid context id: %d", contextID)
   379  	}
   380  
   381  	cs.responseHeaders = cloneWithLowerCaseMapKeys(headers)
   382  	cs.action = internal.ProxyOnResponseHeaders(contextID, len(headers), endOfStream)
   383  	return cs.action
   384  }
   385  
   386  // impl HostEmulator
   387  func (h *httpHostEmulator) CallOnRequestTrailers(contextID uint32, trailers [][2]string) types.Action {
   388  	cs, ok := h.httpStreams[contextID]
   389  	if !ok {
   390  		log.Fatalf("invalid context id: %d", contextID)
   391  	}
   392  
   393  	cs.requestTrailers = cloneWithLowerCaseMapKeys(trailers)
   394  	cs.action = internal.ProxyOnRequestTrailers(contextID, len(trailers))
   395  	return cs.action
   396  }
   397  
   398  // impl HostEmulator
   399  func (h *httpHostEmulator) CallOnResponseTrailers(contextID uint32, trailers [][2]string) types.Action {
   400  	cs, ok := h.httpStreams[contextID]
   401  	if !ok {
   402  		log.Fatalf("invalid context id: %d", contextID)
   403  	}
   404  
   405  	cs.responseTrailers = cloneWithLowerCaseMapKeys(trailers)
   406  	cs.action = internal.ProxyOnResponseTrailers(contextID, len(trailers))
   407  	return cs.action
   408  }
   409  
   410  // impl HostEmulator
   411  func (h *httpHostEmulator) CallOnRequestBody(contextID uint32, body []byte, endOfStream bool) types.Action {
   412  	cs, ok := h.httpStreams[contextID]
   413  	if !ok {
   414  		log.Fatalf("invalid context id: %d", contextID)
   415  	}
   416  
   417  	cs.requestBody = append(cs.requestBodyBuffer, body...)
   418  	cs.action = internal.ProxyOnRequestBody(contextID,
   419  		len(body), endOfStream)
   420  	if cs.action == types.ActionPause {
   421  		// Buffering requested
   422  		cs.requestBodyBuffer = cs.requestBody
   423  	} else {
   424  		cs.requestBodyBuffer = nil
   425  	}
   426  	return cs.action
   427  }
   428  
   429  // impl HostEmulator
   430  func (h *httpHostEmulator) CallOnResponseBody(contextID uint32, body []byte, endOfStream bool) types.Action {
   431  	cs, ok := h.httpStreams[contextID]
   432  	if !ok {
   433  		log.Fatalf("invalid context id: %d", contextID)
   434  	}
   435  
   436  	cs.responseBody = append(cs.responseBodyBuffer, body...)
   437  	cs.action = internal.ProxyOnResponseBody(contextID,
   438  		len(body), endOfStream)
   439  	if cs.action == types.ActionPause {
   440  		// Buffering requested
   441  		cs.responseBodyBuffer = cs.responseBody
   442  	} else {
   443  		cs.responseBodyBuffer = nil
   444  	}
   445  	return cs.action
   446  }
   447  
   448  // impl HostEmulator
   449  func (h *httpHostEmulator) CompleteHttpContext(contextID uint32) {
   450  	internal.ProxyOnLog(contextID)
   451  	internal.ProxyOnDelete(contextID)
   452  }
   453  
   454  // impl HostEmulator
   455  func (h *httpHostEmulator) GetCurrentHttpStreamAction(contextID uint32) types.Action {
   456  	stream, ok := h.httpStreams[contextID]
   457  	if !ok {
   458  		log.Fatalf("invalid context id: %d", contextID)
   459  	}
   460  	return stream.action
   461  }
   462  
   463  // impl HostEmulator
   464  func (h *httpHostEmulator) GetCurrentRequestHeaders(contextID uint32) [][2]string {
   465  	stream, ok := h.httpStreams[contextID]
   466  	if !ok {
   467  		log.Fatalf("invalid context id: %d", contextID)
   468  	}
   469  	return stream.requestHeaders
   470  }
   471  
   472  // impl HostEmulator
   473  func (h *httpHostEmulator) GetCurrentResponseHeaders(contextID uint32) [][2]string {
   474  	stream, ok := h.httpStreams[contextID]
   475  	if !ok {
   476  		log.Fatalf("invalid context id: %d", contextID)
   477  	}
   478  	return stream.responseHeaders
   479  }
   480  
   481  // impl HostEmulator
   482  func (h *httpHostEmulator) GetCurrentRequestBody(contextID uint32) []byte {
   483  	stream, ok := h.httpStreams[contextID]
   484  	if !ok {
   485  		log.Fatalf("invalid context id: %d", contextID)
   486  	}
   487  	return stream.requestBody
   488  }
   489  
   490  // impl HostEmulator
   491  func (h *httpHostEmulator) GetCurrentResponseBody(contextID uint32) []byte {
   492  	stream, ok := h.httpStreams[contextID]
   493  	if !ok {
   494  		log.Fatalf("invalid context id: %d", contextID)
   495  	}
   496  	return stream.responseBody
   497  }
   498  
   499  // impl HostEmulator
   500  func (h *httpHostEmulator) GetSentLocalResponse(contextID uint32) *LocalHttpResponse {
   501  	return h.httpStreams[contextID].sentLocalResponse
   502  }
   503  
   504  // impl HostEmulator
   505  func (h *httpHostEmulator) GetProperty(path []string) ([]byte, error) {
   506  	if len(path) == 0 {
   507  		log.Printf("path must not be empty")
   508  		return nil, internal.StatusToError(internal.StatusBadArgument)
   509  	}
   510  	var ret *byte
   511  	var retSize int
   512  	raw := internal.SerializePropertyPath(path)
   513  
   514  	err := internal.StatusToError(internal.ProxyGetProperty(&raw[0], len(raw), &ret, &retSize))
   515  	if err != nil {
   516  		return nil, err
   517  	}
   518  	return internal.RawBytePtrToByteSlice(ret, retSize), nil
   519  }
   520  
   521  // impl HostEmulator
   522  func (h *httpHostEmulator) GetPropertyMap(path []string) ([][2]string, error) {
   523  	b, err := h.GetProperty(path)
   524  	if err != nil {
   525  		return nil, err
   526  	}
   527  
   528  	return internal.DeserializeMap(b), nil
   529  }
   530  
   531  // impl HostEmulator
   532  func (h *httpHostEmulator) SetProperty(path []string, data []byte) error {
   533  	if len(path) == 0 {
   534  		log.Printf("path must not be empty")
   535  		return internal.StatusToError(internal.StatusBadArgument)
   536  	} else if len(data) == 0 {
   537  		log.Printf("data must not be empty")
   538  		return internal.StatusToError(internal.StatusBadArgument)
   539  	}
   540  	raw := internal.SerializePropertyPath(path)
   541  	return internal.StatusToError(internal.ProxySetProperty(
   542  		&raw[0], len(raw), &data[0], len(data),
   543  	))
   544  }