Repository: RasaHQ/rasa

Issue:
Action server returning 404
<!-- THIS INFORMATION IS MANDATORY - YOUR ISSUE WILL BE CLOSED IF IT IS MISSING. If you don't know your Rasa Core version, use `pip list | grep rasa`. Removing the below information is allowed for FEATURE REQUESTS. --> 

**Rasa version**:1.0.1

**Python version**: 3.6.7

**Operating system** (windows, osx, ...):ubuntu 16.04

**Issue**:
I start the action_end point server .. the log is as follows:

``
rasa run actions --cors "*"
2019-05-29 14:53:05 INFO     rasa_sdk.endpoint  - Starting action endpoint server...
/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa_core_sdk/__init__.py:12: UserWarning: The 'rasa_core_sdk' package has been renamed. You should change your imports to use 'rasa_sdk' instead.
  UserWarning,
2019-05-29 14:53:05 INFO     rasa_sdk.executor  - Registered function for 'validate_inputs'.
2019-05-29 14:53:05 INFO     rasa_sdk.executor  - Registered function for 'action_bye'.
2019-05-29 14:53:05 INFO     rasa_sdk.executor  - Registered function for 'action_restarted'.
2019-05-29 14:53:05 INFO     rasa_sdk.executor  - Registered function for 'action_slot_reset'.
2019-05-29 14:53:05 INFO     rasa_sdk.executor  - Registered function for 'appointment_form'.
2019-05-29 14:53:05 INFO     rasa_sdk.endpoint  - Action endpoint is up and running. on ('0.0.0.0', 5055)
```





**Content of configuration file (config.yml)**:
```yml
language: en

pipeline:
  - name: WhitespaceTokenizer
  - name: CRFEntityExtractor
  - name: EntitySynonymMapper
  - name: CountVectorsFeaturizer
    token_pattern: (?u)\b\w+\b
  - name: EmbeddingIntentClassifier
  - name: DucklingHTTPExtractor
    url: http://localhost:8000
    dimensions:
      - time

policies:
  - name: KerasPolicy
    epochs: 100
    max_history: 5
  - name: MemoizationPolicy
    max_history: 5
  - name: FormPolicy 

```

**Content of domain file (domain.yml)** (if used & relevant):
```yaml
actions:
- utter_greet
- utter_bye
- utter_schedule_appointment
- utter_affirmative
- utter_incorrect_dept
- action_bye
- action_slot_reset
- action_split_date_time
- validate_inputs
- utter_ask_dept
- utter_ask_date
- utter_ask_name
- utter_incorrect_time


slots:
    dept:
      type: text
    date_slot:
      type: unfeaturized
    time_slot:
      type: unfeaturized
    doc_slot:
      type: unfeaturized
    time:
      type: unfeaturized
    name:
      type : unfeaturized
    requested_slot:
      type: unfeaturized  

intents:
- schedule_appointment:
    use_entities : True
- greet
- bye
- affirmative
 
entities:
- dept
- time
- name
templates:
    utter_greet:
    - 'Hello there ! How can I help?'
    utter_bye:
    - 'Bye! Take care'
    utter_ask_dept:
    - 'With which speciality?'
    utter_ask_date:
    - 'On which date would you like to schedule the appointment?'
    utter_ask_time:
    - 'At what time would you like to schedule the appointment?'
    utter_ask_name:
    - 'In whose name would you like to schedule the appointment?'
    utter_incorrect_dept:
    - 'Sorry this speciality does not exist. You will have to enter the speciality again.'
    utter_incorrect_time:
    - 'Sorry! No doctor is available at date and time that you have entered. Please enter the date and time again'
    utter_incorrect_date:
    - 'Sorry the date you have entered is incorrect. Please enter the date and time again'    
    utter_schedule_appointment:
    - 'Shall I confirm your appointment with {doc_slot} in the {dept} department on {date_slot} in the {time_slot}?'
    utter_affirmative:
    - 'Thank you! I am confirming your appointment with {doc_slot} in the {dept} department on {date_slot} in the {time_slot}'
    utter_finish:
    - 'thank you for entering the details'   
    
forms:
- appointment_form


```
My endpoints.yml

```
action_endpoint:
  url: "http://localhost:5055/webhook/"
```

This is the ValidateInputs function
```
class ValidateInputs(Action):
    def name(self):
        return 'validate_inputs'

    .........
```
During interactive learning, when i try to run the validate_inputs actions, i get the following error:
```
What is the next action of the bot?  0.00 validate_inputs                                                                                                                                                       
Thanks! The bot will now run validate_inputs.

2019-05-29 14:53:46 DEBUG    rasa.core.tracker_store  - Recreating tracker for id '5cf18e01f02242af875ebc33d557cda0'
2019-05-29 14:53:46 DEBUG    rasa.core.tracker_store  - Recreating tracker for id '5cf18e01f02242af875ebc33d557cda0'
2019-05-29 14:53:46 DEBUG    rasa.core.actions.action  - Calling action endpoint to run action 'validate_inputs'.
2019-05-29 14:53:46 ERROR    rasa.core.processor  - Encountered an exception while running action 'validate_inputs'. Bot will continue, but the actions events are lost. Make sure to fix the exception in your custom code.
2019-05-29 14:53:46 DEBUG    rasa.core.processor  - Failed to execute custom action.
Traceback (most recent call last):
  File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/actions/action.py", line 410, in run
    json=json_body, method="post", timeout=DEFAULT_REQUEST_TIMEOUT
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/utils/endpoints.py", line 140, in request
    resp.status, resp.reason, await resp.content.read()
rasa.utils.endpoints.ClientResponseError: 404, NOT FOUND, body='b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>\n''

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/processor.py", line 431, in _run_action
    events = await action.run(output_channel, nlg, tracker, self.domain)
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/actions/action.py", line 432, in run
    raise Exception("Failed to execute custom action.") from e
Exception: Failed to execute custom action.
2019-05-29 14:53:46 DEBUG    rasa.core.processor  - Action 'validate_inputs' ended with events '[]'
```
The action server returns
```
127.0.0.1 - - [2019-05-29 14:53:46] "POST /webhook/ HTTP/1.1" 404 373 0.001859

```
Interestingly the action server did run utter_greet which comes before validate_inputs in the sequence
```

 #    Bot                                                                                                                                                            You        
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 1    action_listen                                                                                                                                                             
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 2                                                                                                                                                                           hi 
                                                                                                                                                             intent: greet 0.89 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 3    utter_greet 1.00                                                                                                                                                          
      Hello there ! How can I help?                                                                                                                                             
      action_listen 1.00                                                                                                                                                        
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
                  

```

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 66d4140289b..421b12df0db 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,6 +23,7 @@ Removed
 Fixed
 -----
 - loading of additional training data with the ``SkillSelector``
+- strip trailing slashes in endpoint URLs
 
 [1.0.7] - 2019-06-06
 ^^^^^^^^^^^^^^^^^^^^
diff --git a/rasa/utils/endpoints.py b/rasa/utils/endpoints.py
index 4d7ebdd4fdb..12b610ac1a3 100644
--- a/rasa/utils/endpoints.py
+++ b/rasa/utils/endpoints.py
@@ -45,7 +45,7 @@ def concat_url(base: Text, subpath: Optional[Text]) -> Text:
     `base`."""
 
     if not subpath:
-        return base
+        return base.rstrip("/")
 
     url = base
     if not base.endswith("/"):


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 71343f666cb..fe6cf813c28 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,5 +1,6 @@
+import pytest
 from aioresponses import aioresponses
-from rasa.utils.endpoints import EndpointConfig
+from rasa.utils.endpoints import EndpointConfig, concat_url
 from tests.utilities import latest_request, json_of_latest_request
 from rasa.utils.common import sort_list_of_dicts_by_first_key
 
@@ -61,3 +62,18 @@ def test_sort_dicts_by_keys():
     actual = sort_list_of_dicts_by_first_key(test_data)
 
     assert actual == expected
+
+
+@pytest.mark.parametrize(
+    "base, subpath, expected_result",
+    [
+        ("https://example.com", None, "https://example.com"),
+        ("https://example.com/test", None, "https://example.com/test"),
+        ("https://example.com/", None, "https://example.com"),
+        ("https://example.com//", None, "https://example.com"),
+        ("https://example.com/", "test", "https://example.com/test"),
+        ("https://example.com/", "test/", "https://example.com/test/"),
+    ],
+)
+def test_concat_url(base, subpath, expected_result):
+    assert concat_url(base, subpath) == expected_result