1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3#*************************************************************************** 4# _ _ ____ _ 5# Project ___| | | | _ \| | 6# / __| | | | |_) | | 7# | (__| |_| | _ <| |___ 8# \___|\___/|_| \_\_____| 9# 10# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 11# 12# This software is licensed as described in the file COPYING, which 13# you should have received as part of this distribution. The terms 14# are also available at https://curl.se/docs/copyright.html. 15# 16# You may opt to use, copy, modify, merge, publish, distribute and/or sell 17# copies of the Software, and permit persons to whom the Software is 18# furnished to do so, under the terms of the COPYING file. 19# 20# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21# KIND, either express or implied. 22# 23# SPDX-License-Identifier: curl 24# 25########################################################################### 26# 27import logging 28import os 29import shutil 30import subprocess 31import time 32from datetime import datetime, timedelta 33import pytest 34 35from testenv import Env, CurlClient, LocalClient 36 37 38log = logging.getLogger(__name__) 39 40 41@pytest.mark.skipif(condition=not Env.curl_has_protocol('ws'), 42 reason='curl lacks ws protocol support') 43class TestWebsockets: 44 45 def check_alive(self, env, timeout=5): 46 curl = CurlClient(env=env) 47 url = f'http://localhost:{env.ws_port}/' 48 end = datetime.now() + timedelta(seconds=timeout) 49 while datetime.now() < end: 50 r = curl.http_download(urls=[url]) 51 if r.exit_code == 0: 52 return True 53 time.sleep(.1) 54 return False 55 56 def _mkpath(self, path): 57 if not os.path.exists(path): 58 return os.makedirs(path) 59 60 def _rmrf(self, path): 61 if os.path.exists(path): 62 return shutil.rmtree(path) 63 64 @pytest.fixture(autouse=True, scope='class') 65 def ws_echo(self, env): 66 run_dir = os.path.join(env.gen_dir, 'ws-echo-server') 67 err_file = os.path.join(run_dir, 'stderr') 68 self._rmrf(run_dir) 69 self._mkpath(run_dir) 70 71 with open(err_file, 'w') as cerr: 72 cmd = os.path.join(env.project_dir, 73 'tests/http/testenv/ws_echo_server.py') 74 args = [cmd, '--port', str(env.ws_port)] 75 p = subprocess.Popen(args=args, cwd=run_dir, stderr=cerr, 76 stdout=cerr) 77 assert self.check_alive(env) 78 yield 79 p.terminate() 80 81 def test_20_01_basic(self, env: Env, ws_echo, repeat): 82 curl = CurlClient(env=env) 83 url = f'http://localhost:{env.ws_port}/' 84 r = curl.http_download(urls=[url]) 85 r.check_response(http_status=426) 86 87 def test_20_02_pingpong_small(self, env: Env, ws_echo, repeat): 88 payload = 125 * "x" 89 client = LocalClient(env=env, name='ws-pingpong') 90 if not client.exists(): 91 pytest.skip(f'example client not built: {client.name}') 92 url = f'ws://localhost:{env.ws_port}/' 93 r = client.run(args=[url, payload]) 94 r.check_exit_code(0) 95 96 # the python websocket server does not like 'large' control frames 97 def test_20_03_pingpong_too_large(self, env: Env, ws_echo, repeat): 98 payload = 127 * "x" 99 client = LocalClient(env=env, name='ws-pingpong') 100 if not client.exists(): 101 pytest.skip(f'example client not built: {client.name}') 102 url = f'ws://localhost:{env.ws_port}/' 103 r = client.run(args=[url, payload]) 104 r.check_exit_code(56) 105 106 # the python websocket server does not like 'large' control frames 107 def test_20_04_data_small(self, env: Env, ws_echo, repeat): 108 client = LocalClient(env=env, name='ws-data') 109 if not client.exists(): 110 pytest.skip(f'example client not built: {client.name}') 111 url = f'ws://localhost:{env.ws_port}/' 112 r = client.run(args=[url, str(0), str(10)]) 113 r.check_exit_code(0) 114 115 # the python websocket server does not like 'large' control frames 116 def test_20_05_data_med(self, env: Env, ws_echo, repeat): 117 client = LocalClient(env=env, name='ws-data') 118 if not client.exists(): 119 pytest.skip(f'example client not built: {client.name}') 120 url = f'ws://localhost:{env.ws_port}/' 121 r = client.run(args=[url, str(120), str(130)]) 122 r.check_exit_code(0) 123 124 # the python websocket server does not like 'large' control frames 125 def test_20_06_data_large(self, env: Env, ws_echo, repeat): 126 client = LocalClient(env=env, name='ws-data') 127 if not client.exists(): 128 pytest.skip(f'example client not built: {client.name}') 129 url = f'ws://localhost:{env.ws_port}/' 130 r = client.run(args=[url, str(65535 - 5), str(65535 + 5)]) 131 r.check_exit_code(0) 132 133 # the python websocket server does not like 'large' control frames 134 def test_20_07_data_large_small_recv(self, env: Env, ws_echo, repeat): 135 client = LocalClient(env=env, name='ws-data', run_env={ 136 'CURL_WS_CHUNK_SIZE': '1024', 137 }) 138 if not client.exists(): 139 pytest.skip(f'example client not built: {client.name}') 140 url = f'ws://localhost:{env.ws_port}/' 141 r = client.run(args=[url, str(65535 - 5), str(65535 + 5)]) 142 r.check_exit_code(0) 143