API definition to websocket target


#1

I’m trying to get a websocket to go through the gateway.

The client is connecting to: ws://echo.websocket.org/ using their reference client code which is a small chunk of JS with an HTML page wrapper. It works correctly using this (and not going through Tyk).

I enabled websockets in Tyk CE (2.3.3) and added a new API definition which I want to route from ws://{host}/socket_test/ to ws://echo.websocket.org.

In the API definition:
If I declare the target url to proxy to ws://, I get “http: proxy error: unsupported protocol scheme “ws””… and a 500 return code.
If I declare the target url to http://, Tyk is not sending it as a websocket upgrade - just a plain http GET - and I get 400 back from the server.

Suggestions?
Craig


#2

Is websockets enabled in your tyk.conf?

I think the issue you might have had was CORS, since you must get Tyk to manage that for you…

Running a simple test, this works locally:

testwww/index.html


<!DOCTYPE html>
  <meta charset="utf-8" />
  <title>WebSocket Test</title>
  <script language="javascript" type="text/javascript">

  var wsUri = "ws://10.0.75.2:8181/ws/";
  var output;

  function init()
  {
    output = document.getElementById("output");
    testWebSocket();
  }

  function testWebSocket()
  {
    websocket = new WebSocket(wsUri);
    websocket.onopen = function(evt) { onOpen(evt) };
    websocket.onclose = function(evt) { onClose(evt) };
    websocket.onmessage = function(evt) { onMessage(evt) };
    websocket.onerror = function(evt) { onError(evt) };
  }

  function onOpen(evt)
  {
    writeToScreen("CONNECTED");
    doSend("WebSocket rocks");
  }

  function onClose(evt)
  {
    writeToScreen("DISCONNECTED");
  }

  function onMessage(evt)
  {
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
    websocket.close();
  }

  function onError(evt)
  {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
  }

  function doSend(message)
  {
    writeToScreen("SENT: " + message);
    websocket.send(message);
  }

  function writeToScreen(message)
  {
    var pre = document.createElement("p");
    pre.style.wordWrap = "break-word";
    pre.innerHTML = message;
    output.appendChild(pre);
  }

  window.addEventListener("load", init, false);

  </script>

  <h2>WebSocket Test</h2>

  <div id="output"></div>

Run this somewhere with:

cd testwww
python -m SimpleHTTPServer 8000

API Definition:

{
    "id": "58ca20ded95a9b01650f661c",
    "name": "ws",
    "slug": "ws",
    "api_id": "271f8325a5054e5c4ae713bf5bf1cc9e",
    "org_id": "589a2058c2e9f9018bad03aa",
    "use_keyless": true,
    "use_oauth2": false,
    "use_openid": false,
    "openid_options": {
        "providers": [],
        "segregate_by_client": false
    },
    "oauth_meta": {
        "allowed_access_types": [],
        "allowed_authorize_types": [],
        "auth_login_redirect": ""
    },
    "auth": {
        "use_param": false,
        "param_name": "",
        "use_cookie": false,
        "cookie_name": "",
        "auth_header_name": ""
    },
    "use_basic_auth": false,
    "enable_jwt": false,
    "use_standard_auth": false,
    "enable_coprocess_auth": false,
    "jwt_signing_method": "",
    "jwt_source": "",
    "jwt_identity_base_field": "",
    "jwt_client_base_field": "",
    "jwt_policy_field_name": "",
    "notifications": {
        "shared_secret": "",
        "oauth_on_keychange_url": ""
    },
    "enable_signature_checking": false,
    "hmac_allowed_clock_skew": -1,
    "base_identity_provided_by": "",
    "definition": {
        "location": "header",
        "key": "x-api-version"
    },
    "version_data": {
        "not_versioned": true,
        "versions": {
            "Default": {
                "name": "Default",
                "expires": "",
                "paths": {
                    "ignored": [],
                    "white_list": [],
                    "black_list": []
                },
                "use_extended_paths": true,
                "extended_paths": {},
                "global_headers": {},
                "global_headers_remove": [],
                "global_size_limit": 0,
                "override_target": ""
            }
        }
    },
    "uptime_tests": {
        "check_list": [],
        "config": {
            "expire_utime_after": 0,
            "service_discovery": {
                "use_discovery_service": false,
                "query_endpoint": "",
                "use_nested_query": false,
                "parent_data_path": "",
                "data_path": "",
                "port_data_path": "",
                "target_path": "",
                "use_target_list": false,
                "cache_timeout": 60,
                "endpoint_returns_list": false
            },
            "recheck_wait": 0
        }
    },
    "proxy": {
        "preserve_host_header": false,
        "listen_path": "/ws/",
        "target_url": "ws://echo.websocket.org/",
        "strip_listen_path": true,
        "enable_load_balancing": false,
        "target_list": [],
        "check_host_against_uptime_tests": false,
        "service_discovery": {
            "use_discovery_service": false,
            "query_endpoint": "",
            "use_nested_query": false,
            "parent_data_path": "",
            "data_path": "hostname",
            "port_data_path": "port",
            "target_path": "/api-slug",
            "use_target_list": false,
            "cache_timeout": 60,
            "endpoint_returns_list": false
        }
    },
    "disable_rate_limit": false,
    "disable_quota": false,
    "custom_middleware": {
        "pre": [],
        "post": [],
        "post_key_auth": [],
        "auth_check": {
            "name": "",
            "path": "",
            "require_session": false
        },
        "response": [],
        "driver": "",
        "id_extractor": {
            "extract_from": "",
            "extract_with": "",
            "extractor_config": {}
        }
    },
    "custom_middleware_bundle": "",
    "cache_options": {
        "cache_timeout": 60,
        "enable_cache": true,
        "cache_all_safe_requests": false,
        "cache_response_codes": [],
        "enable_upstream_cache_control": false
    },
    "session_lifetime": 0,
    "active": true,
    "auth_provider": {
        "name": "",
        "storage_engine": "",
        "meta": {}
    },
    "session_provider": {
        "name": "",
        "storage_engine": "",
        "meta": null
    },
    "event_handlers": {
        "events": {}
    },
    "enable_batch_request_support": false,
    "enable_ip_whitelisting": false,
    "allowed_ips": [],
    "dont_set_quota_on_create": false,
    "expire_analytics_after": 0,
    "response_processors": [],
    "CORS": {
        "enable": true,
        "allowed_origins": [
            "*"
        ],
        "allowed_methods": [
            "*"
        ],
        "allowed_headers": [
            "*"
        ],
        "exposed_headers": [
            "*"
        ],
        "allow_credentials": false,
        "max_age": 24,
        "options_passthrough": true,
        "debug": false
    },
    "domain": "",
    "do_not_track": false,
    "tags": [],
    "enable_context_vars": false
}

(Obviously change the proxy address).

Then browse to: http://127.0.0.1:8000/

And I see:

WebSocket Test

CONNECTED

SENT: WebSocket rocks

RESPONSE: WebSocket rocks

DISCONNECTED

In my console I can see that it has connected to my docker instance (which is a VM on a different IP address), so it is proxying correctly via Tyk.


#3

The only issue was the tyk.conf file - I had websockets enabled but not in the http_server_options block.
Works as expected now. Thanks for your help!
Craig


#4

Hi,

I am having the same issue, getting websockets running, and have imported the API definition above and used the code from echo.websocket.org (changing the URL as appropriate). websockets is enabled in tyk.conf.

When the sockect connects to my hosted Tyk it gets a 500 error, I don’t know if there is any way of expanding this to get a more meaningful message out.

On further investigation these error messages are appearing
[Apr 20 09:54:46] ERROR proxy: http: proxy error: unsupported protocol scheme “ws” api_id=f1b301f84f8d410d5087ba9e8e678dcf org_id=58f777b3e13823092cf5c10d server_name=echo.websocket.org user_id= user_ip=79.66.176.17 user_name=
[Apr 20 09:54:46] ERROR gateway: request error: There was a problem proxying the request api_id=f1b301f84f8d410d5087ba9e8e678dcf org_id=58f777b3e13823092cf5c10d path=/ server_name=ws://echo.websocket.org/ user_id= user_ip=79.66.176.17

My tyk.conf contains

“http_server_options”: {
“override_defaults”: false,
“read_timeout”: 0,
“write_timeout”: 0,
“use_ssl”: false,
“use_ssl_le”: false,
“enable_websockets”: true,
“certificates”: null,
“server_name”: “”,
“min_version”: 0,
“flush_interval”: 0,
“skip_url_cleaning”: false
},


#5

That’s the same error message sequence I was seeing when I didn’t have enable_websockets in http_server_options. If the option isn’t loaded Tyk doesn’t know how to use the ws:// prefix.

What version of Tyk are you using? This is a fairly recent feature.


#6

Hi Craig

2.3.4, the latest stable build, as you can see it looks like websockets should be enabled, and I think the conf file is being picked up as I have changed the listen port and it was happy with that.


#7

There must still be something wrong with your tyk.conf, I’ve verified that the example above works just fine, so it’s not a regression in the build. Can you share your full tyk.conf?


#8

Here you go

{
“listen_port”: 80,
“node_secret”: “352d20ee67be67f6340b4c0605b044b7”,
“secret”: “352d20ee67be67f6340b4c0605b044b7”,
“template_path”: “/opt/tyk-gateway/templates”,
“tyk_js_path”: “/opt/tyk-gateway/js/tyk.js”,
“use_db_app_configs”: true,
“db_app_conf_options”: {
“connection_string”: “”,
“node_is_segmented”: false,
“tags”: []
},
“disable_dashboard_zeroconf”: false,
“app_path”: “/opt/tyk-gateway/apps”,
“middleware_path”: “/opt/tyk-gateway/middleware”,
“storage”: {
“type”: “redis”,
“host”: “localhost”,
“port”: 6379,
“username”: “”,
“password”: “”,
“database”: 0,
“optimisation_max_idle”: 2000,
“optimisation_max_active”: 4000
},
“enable_analytics”: true,
“analytics_config”: {
“type”: “mongo”,
“pool_size”: 100,
“csv_dir”: “/tmp”,
“mongo_url”: “”,
“mongo_db_name”: “”,
“mongo_collection”: “”,
“purge_delay”: 100,
“ignored_ips”: [],
“enable_detailed_recording”: true,
“enable_geo_ip”: false,
“geo_ip_db_path”: “”,
“normalise_urls”: {
“enabled”: true,
“normalise_uuids”: true,
“normalise_numbers”: true,
“custom_patterns”: []
}
},
“health_check”: {
“enable_health_checks”: false,
“health_check_value_timeouts”: 60
},
“optimisations_use_async_session_write”: true,
“allow_master_keys”: false,
“policies”: {
“policy_source”: “service”,
“policy_connection_string”: “”,
“policy_record_name”: “tyk_policies”,
“allow_explicit_policy_id”: true
},
“hash_keys”: true,
“suppress_redis_signal_reload”: false,
“use_redis_log”: true,
“close_connections”: true,
“enable_non_transactional_rate_limiter”: true,
“enable_sentinel_rate_limiter”: false,
“experimental_process_org_off_thread”: true,
“local_session_cache”: {
“disable_cached_session_state”: false
},
“http_server_options”: {
“override_defaults”: false,
“read_timeout”: 0,
“write_timeout”: 0,
“use_ssl”: false,
“use_ssl_le”: false,
“enable_websockets”: true,
“certificates”: null,
“server_name”: “”,
“min_version”: 0,
“flush_interval”: 0,
“skip_url_cleaning”: false
},
“uptime_tests”: {
“disable”: false,
“config”: {
“enable_uptime_analytics”: true,
“failure_trigger_sample_size”: 3,
“time_wait”: 300,
“checker_pool_size”: 50
}
},
“hostname”: “”,
“enable_custom_domains”: true,
“enable_jsvm”: true,
“oauth_redirect_uri_separator”: “;”,
“coprocess_options”: {
“enable_coprocess”: false,
“coprocess_grpc_server”: “”
},
“pid_file_location”: “./tyk-gateway.pid”,
“allow_insecure_configs”: true,
“public_key_path”: “”,
“close_idle_connections”: false,
“allow_remote_config”: false,
“enable_bundle_downloader”: true,
“bundle_base_url”: “”,
“global_session_lifetime”: 100,
“force_global_session_lifetime”: false,
“max_idle_connections_per_host”: 100
}


#9

Ok, not sure how you got that tyk.conf, you can compress that http_options down to just this:

"http_server_options": {
        "enable_websockets": true
},

I think that some of the other options there might be interfering, if in doubt, use this as your base template:


#10

Thanks Martin, that gets things working. I think the config file I sent came from install/tyk.conf with added http_options. I manages to overwrite the one originally generated by running tyk directly.

Interstingly, or not, the test file works with Chrome but not Mozilla, nothing to do with Tyk.