Git Product home page Git Product logo

elixir-sippet's Introduction

Sippet

Build Status Coverage Status Docs Status Hex version Hex.pm Code Triagers Badge

An Elixir library designed to write Session Initiation Protocol middleware.

Introduction

SIP is a very flexible protocol that has great depth. It was designed to be a general-purpose way to set up real-time multimedia sessions between groups of participants. It is a text-based protocol modeled on the request/response model used in HTTP. This makes it easy to debug because the messages are relatively easy to construct and easy to see.

Sippet is designed as a simple SIP middleware library, aiming the developer to write any kind of function required to register users, get their availability, check capabilities, setup and manage sessions. On the other hand, Sippet does not intend to provide any feature available in a fully functional SIP UAC/UAS, proxy server, B2BUA, SBC or application; instead, it has only the essential building blocks to build any kind of SIP middleware.

Overview

One of the most central parts of Sippet is the Sippet.Message. Instead of many headers that you end up having to parse by yourself, there's an internal parser written in C++ (an Erlang NIF) that does all the hard work for you. This way, the Sippet.Message.headers is a key-value simple Map where the key is the header name, and the value varies accordingly the header type. For instance, the header :cseq has the form {sequence :: integer, method} where the method is an atom with the method name (like :invite).

Message routing is performed just manipulating Sippet.Message headers; everything else is performed by these layers in a very standard way. That means you may not be able to build some non-standard behaviors, like routing the message to a given host that wasn't correctly added to the topmost Via header.

As Sippet is a simple SIP library, the developer has to understand the protocol very well before writing a middleware. This design decision came up because all attempts to hide any inherent SIP complexity by other frameworks have failed.

There is no support for plugins or hooks, these case be implemented easily with Elixir behaviors and macros, and the developer may custom as he likes. Incoming messages and transport errors are directed to a Sippet.Core module behavior.

Finally, there is no support for many different transport protocols; a simple Sippet.Transports.UDP implementation is provided, which is enough for general purpose SIP middleware. Transport protocols can be implemented quite easily using the same logic of Sippet.Transport.UDP.

Installation

The package can be installed from Hex as:

  1. Add sippet to your list of dependencies in mix.exs:
def deps do
  [{:sippet, "~> 1.0"}]
end
  1. Give a name to your stack and build it:
# Creates a :mystack Sippet instance
Sippet.start_link(name: :mystack)

# The below will create a default UDP transport listening on 0.0.0.0:5060/udp
Sippet.Transports.UDP.start_link(name: :mystack)
  1. Create a Sippet.Core and register it:
defmodule MyCore do
  use Sippet.Core

  def receive_request(incoming_request, server_key) do
    # route the request to your UA or proxy process
  end

  def receive_response(incoming_response, client_key) do
    # route the response to your UA or proxy process
  end

  def receive_error(reason, client_or_server_key) do
    # route the error to your UA or proxy process
  end
end

Sippet.register_core(:mystack, MyCore)

Voilà! The SIP stack will be listening on the indicated address and port, and your MyCore module will receive callbacks from it whenever a SIP message arrives on it.

You may send messages this way:

request = %Message{
  start_line: RequestLine.new(:options, "sip:sip.example.com"),
  headers: %{
    via: [
      {{2, 0}, :udp, {"localhost", 5060}, %{"branch" => Message.create_branch()}}
    ],
    from: {"", URI.parse!("sip:localhost"), %{"tag" => Message.create_tag()}},
    to: {"", URI.parse!("sip:sip.example.com"), %{}},
    cseq: {1, :options},
    user_agent: "Sippet/1.0",
    call_id: Message.create_call_id()
  }
}

Sippet.send(:sippet, request)

If you prefer to specify messages directly in wire format, here you go:

request =
  """
  OPTIONS sip:sip.example.com SIP/2.0
  Via: SIP/2.0/UDP localhost:5060;branch=#{Message.create_branch()}
  From: sip:localhost;tag=#{Message.create_tag()}
  To: sip:sip.example.com
  CSeq: 1 OPTIONS
  User-Agent: Sippet/1.0
  Call-ID: #{Message.create_call_id()}
  """ |> Message.parse!()

Sippet.send(:sippet, request)

Further documentation can found at https://hexdocs.pm/sippet.

Headers format

# Definitions
# ======================================================================================
@type type :: String.t
@type subtype :: String.t
@type token :: String.t
@type name :: String.t
@type scheme :: String.t
@type parameters :: %{String.t => String.t}
@type uri :: Sippet.URI.t
@type major :: integer
@type minor :: integer
@type display_name :: String.t
@type string :: String.t
@type timestamp :: double
@type delay :: double
@type protocol :: atom | String.t
@type method :: atom | String.t


# Header Name             Type
# ======================================================================================
@type headers :: %{
  :accept              => [{{type, subtype}, parameters}, ...],
  :accept_encoding     => [{token, parameters}, ...],
  :accept_language     => [{token, parameters}, ...],
  :alert_info          => [{uri, parameters}, ...],
  :allow               => [token, ...],
  :authentication_info => %{name => value},
  :authorization       => [{scheme, parameters}, ...],
  :call_id             => token,
  :call_info           => [{uri, parameters}, ...],
  :contact             => "*" | [{display_name, uri, parameters}, ...],
  :content_disposition => {token, parameters},
  :content_encoding    => [token, ...],
  :content_language    => [token, ...],
  :content_length      => integer,
  :content_type        => {{type, subtype}, parameters},
  :cseq                => {integer, method},
  :date                => NaiveDateTime.t,
  :error_info          => [{uri, parameters}, ...],
  :expires             => integer,
  :from                => {display_name, uri, parameters},
  :in_reply_to         => [token, ...],
  :max_forwards        => integer,
  :mime_version        => {major, minor},
  :min_expires         => integer,
  :organization        => string,
  :p_asserted_identity => [{display_name, uri, parameters}, ...],
  :priority            => token,
  :proxy_authenticate  => [{scheme, parameters}, ...],
  :proxy_authorization => [{scheme, parameters}, ...],
  :proxy_require       => [token, ...],
  :reason              => [{token, parameters}, ...],
  :record_route        => [{display_name, uri, parameters}, ...],
  :reply_to            => {display_name, uri, parameters},
  :require             => [token, ...],
  :retry_after         => {integer, comment, parameters},
  :route               => [{display_name, uri, parameters}, ...],
  :server              => string,
  :subject             => string,
  :supported           => [token, ...],
  :timestamp           => {timestamp, delay},
  :to                  => {display_name, uri, parameters},
  :unsupported         => [token, ...],
  :user_agent          => string,
  :via                 => [{{major, minor}, protocol, {address, port}, parameters}, ...],
  :warning             => [{integer, agent, text}, ...],
  :www_authenticate    => [{scheme, parameters}, ...],
  String.t             => [String.t, ...]
}

Copyright

Copyright (c) 2016-2020 Guilherme Balena Versiani. See LICENSE for further details.

elixir-sippet's People

Contributors

balena avatar blackham avatar brendanball avatar martinos avatar mickel8 avatar nitrino avatar pramsky avatar sgfn avatar xadhoom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

elixir-sippet's Issues

error: ‘numeric_limits’ is not a member of ‘std’

Problem:

/home/your_mom/dev/my_bed/deps/sippet/c_src/utils.cc: In static member function ‘static bool {anonymous}::IteratorRangeToNumber<IteratorRangeToNumberTraits>::Invoke(const_iterator, const_iterator, value_type*)’:
/home/your_mom/dev/my_bed/deps/sippet/c_src/utils.cc:99:17: error: ‘numeric_limits’ is not a member of ‘std’
99 |       if (!std::numeric_limits<value_type>::is_signed) {

Hack:
Edit the ./deps/sippet/c_src/utils.cc and add #include < limits >

#include <string>
#include <iostream>
#include <limits>

Fix:
Eh... I'm happy with the hack.

System:
Arch Linux

brett@beefstick: /tmp $ uname -a
Linux beefstick 6.1.15-1-lts #1 SMP PREEMPT_DYNAMIC Fri, 03 Mar 2023 12:22:08 +0000 x86_64 GNU/Linux
brett@beefstick: /tmp $ gcc --version
gcc (GCC) 12.2.1 20230201
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Error, module could not be loaded", Sippet.Transaction

When I send an OPTION request, I get the following error message:

sipsak -vv -c sip:[email protected] -s sip:nobody@localhost
18:49:09.272 [error] GenStateMachine {Sippet.Transactions.Registry, ~K[z9hG4bK.74bac126|:options|127.0.0.1:62145]} terminating
** (ErlangError) Erlang error: {:"module could not be loaded", Sippet.Transaction}
    (sippet) lib/sippet/transactions/server/non_invite.ex:12: Sippet.Transactions.Server.NonInvite.trying/3
    (stdlib) gen_statem.erl:1240: :gen_statem.call_state_function/5
    (stdlib) gen_statem.erl:1058: :gen_statem.loop_event_enter/11
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Here is my Core implementation:

defmodule MyCore do
  use Sippet.Core
  require Logger
  alias Sippet.Transaction
  alias Sippet.Message

  def receive_request(request, server_key) do
    Logger.info("logging request: #{inspect(request)}")
    request |> Message.to_response(302) |> Transaction.send_response(server_key)
  end
end

Maybe I don't understand exactly how to declare a Core module.

Error sending response

When I send a request to my app, I get the following error:

07:59:58.395 [error] GenServer #PID<0.199.0> terminating
** (FunctionClauseError) no function clause matching in :inet_udp.getserv/1
    (kernel) inet_udp.erl:38: :inet_udp.getserv({62421, ""})
    (kernel) gen_udp.erl:127: :gen_udp.send/4
    (sippet) lib/sippet/transports/udp/sender.ex:36: Sippet.Transports.UDP.Sender.handle_cast/2
    (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:686: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

This is caused by this line:

https://github.com/balena/elixir-sippet/blob/master/lib/sippet/transports.ex#L85

The Integer.parse/1 function now returns a tuple.

https://hexdocs.pm/elixir/Integer.html#parse/2

I think the solution would be something like

 %{"rport" => rport} -> Integer.parse(rport) |> elem(0)

receive_request got called but client side still timeouts

Hello hello. First, thanks for implementing this!

I added config Sippet.Core and the receive_request callback got called but the client still timeouts. It is the Telephone macOS client. Could you give me some hint on what to do next?

Thank you!

Improve explanation for the Sippet.send function in README.md

I am trying migrate my app to the latest version of Sippet and I don't understand how to send a response back to the caller.

When I read the README file, I see

Sippet.send(:sippet, request)

I don't understand what is the :sippet atom. Should it be the stack name?

Prior to the migration my Core module add a function as such:

  def receive_request(request, server_key) do
    request
    |> except_to_500(&Auth.auth(&1))
    |> Transactions.send_response(server_key)
  end

I am wondering how can I migrate this to the latest version of the library.

Client transaction already exists

First of all, this looks like a great application, still investigating, but thanks for making this available!

I have created a simple project using elixir-sippet 1.0.7.

My aim is to create a program that does (many) outbound registrations with authentication for different contacts (so one contact per registration).

I am able to send out a REGISTER request, by creating one as follows:

req = """
REGISTER sip:foo.com SIP/2.0
Via: SIP/2.0/UDP 192.168.64.1:5060;branch=#{Sippet.Message.create_branch()}
From: <sip:[email protected]>;tag=#{Sippet.Message.create_tag()}
To: <sip:[email protected]>
CSeq: 1 REGISTER
Call-ID: #{Sippet.Message.create_call_id()}
Contact: <sip:[email protected]:5060>
Expires: 300
""" |> Sippet.Message.parse!()

And then sending it out as follows:
Sippet.send :mystack, req

I see the request being sent out to the registrar and a 401 challenge being responded by it.

I have a simple Core registered that just prints out the arguments incoming_response and client_key passed to the receive_response function.

Now I'd like to send a second request, but with the proper Authorization header in it.

First, I tried to generate a new request based on the sent request and received response:
{:ok, auth_req} = Sippet.DigestAuth.make_request req, resp, fn(_realm) -> {:ok, "phone1", "topsecret"} end

Then incremented the cseq by one as follows:
auth_req_inc_cseq = put_in auth_req.headers.cseq, {2, :register}

But when then trying to send that request, it says the client transaction already exists.

I was thinking I may need to change the via branch, but still. If the response to a request is received, where does the transaction then remain ? Should I remove it myself ?

What would be the proper way to go for this ?

Thanks!

New release

Is it possible to make a release with a bug fix from #34?

Support for TCP transport

We will be using a sippet library, but noticed there is no tcp transport. Is adding tcp transport something hard to current implementation?

Unable to link Sippet on Apple Silicon with LDFLAGS set

Hi,
It appears that because of the structure of the Makefile, having LDFLAGS set in the env makes the compilation of Sippet fail on Apple Silicon.

In our app, we're using Sippet as well as fast_tls. The issue is, fast_tls needs LDFLAGS to contain the path to the OpenSSL library (i.e. LDFLAGS=-L/opt/homebrew/opt/openssl/lib), and will not compile otherwise. When we try to compile everything at once, though, linking of Sippet fails because the Makefile uses conditional variable assignment ?= and does not overwrite/append to LDFLAGS if it is already set -- which is the case.

As far as I've tested, the only linker flag necessary for Sippet to compile successfully with this setup is -undefined suppress.

The most straightforward way of alleviating this issue I can think of is to always append the -undefined suppress flag, since it is necessary to link anyway. This is a simple fix, and doesn't change the general behaviour of the Makefile:

diff --git a/c_src/Makefile b/c_src/Makefile
index c20a402..a6dc5f1 100644
--- a/c_src/Makefile
+++ b/c_src/Makefile
@@ -73,7 +73,9 @@ endif
        CC ?= cc
        CFLAGS ?= -O3 -std=c11 $(ARCHFLAGS) -fstack-protector -Wall -Wmissing-prototypes
        CXXFLAGS ?= -O3 -std=c++11 $(ARCHFLAGS) -fstack-protector -Wall
-       LDFLAGS ?= $(ARCHFLAGS) -flat_namespace -undefined suppress
+       LDFLAGS ?= $(ARCHFLAGS) -flat_namespace
+       # Necessary to link successfully on Apple Silicon
+       LDFLAGS += -undefined suppress
 else ifeq ($(PLATFORM),freebsd)
        CC ?= cc
        CFLAGS ?= -O3 -std=c11 -finline-functions -fstack-protector -Wall -Wmissing-prototypes

Please let me know whether you're OK with this solution, and I'll be happy to make the relevant PR.

Logs from the failed linking for reference:

ld: Undefined symbols:
  _enif_alloc_binary, referenced from:
      (anonymous namespace)::ParseMultipleUriParams(enif_environment_t*, std::__1::__wrap_iter<char const*>, std::__1::__wrap_iter<char const*>) in parser.o
      ...
  _enif_get_list_cell, referenced from:
      parse_wrapper(enif_environment_t*, int, unsigned long const*) in parser.o
  _enif_get_map_value, referenced from:
      parse_wrapper(enif_environment_t*, int, unsigned long const*) in parser.o
  [...] etc.

Sample MyCore implementation

Could you please provide a sample MyCore implementation? Reason I'm asking is that I've done all other configuration/setup (as explained in the readme) and I'm able to see started plug 127.0.0.1:5060/udp logged message when I start the application, however none of the log messages from my custom core implementation appear when I send a sample INVITE request.

For the reference - my custom core implementation:

defmodule ZCore do
  require Logger
  alias Sippet.Transactions
  alias Sippet.Message

  @behaviour Sippet.Core

  require Logger


  @impl Sippet.Core
  def receive_request(req, serverTxKey) do
    Logger.debug(fn -> "#{inspect self()} receive_request:: req => #{inspect req}; serverTxKey => #{inspect serverTxKey}" end)
    req |> Message.to_response(100) |> Transactions.send_response(serverTxKey)
  end

  @impl Sippet.Core
  def receive_response(resp, clientTxKey) do
    Logger.debug(fn -> "#{inspect self()} receive_response:: resp => #{inspect resp}; clientTxKey => #{inspect clientTxKey}" end)
  end

  @impl Sippet.Core
  def receive_error(reason, txKey) do
    Logger.debug(fn -> "#{inspect self()} receive_error:: reason => #{inspect reason}; txKey => #{inspect txKey}" end)
  end

end

As you might have guessed - an Elixir beginner here so any other relevant comments/suggestions are welcome.

Params with empty value should not display =

I am receiving request that has a Via header that has a rport flag. Here is the original header

Via: SIP/2.0/UDP 205.205.74.6:5060;rport;branch=z9hG4bK-26320-1-0

When I return the response, the Via header is serialized this way:

Via: SIP/2.0/UDP 205.205.74.6:5060;rport=;received=127.0.0.1;branch=z9hG4bK-26320-1-0

Note that the rport= param is invalid. https://www.tech-invite.com/fo-abnf/tinv-fo-abnf-sip-h-via.html#via-params.

I think that the value of a parameter cannot be an empty string.

:from does not have tag

When I send an OPTION request to the server using sipsak

sipsak -vv -c sip:[email protected] -s sip:nobody@localhost

I get the following warning from the server:

discarded request, ":from does not have tag"

I looked at the source code and it seems that the tag is not even parsed. Do I miss something ?

I tried many other request and I had the same issue.

Tests does not pass on Elixir 1.7

Here the error that I've got while running mix test on the master branch:

== Compilation error in file test/transaction_client_invite_test.exs ==
** (CompileError) test/transaction_client_invite_test.exs:73: cannot invoke remote function :erlang.*/2 inside match
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    (elixir) expanding macro: Kernel.|>/2

When I try to run test - Compilation fails on Mac

cc /Users/krishna/projects/elixir-sippet/c_src/string_piece.o /Users/krishna/projects/elixir-sippet/c_src/utils.o /Users/krishna/projects/elixir-sippet/c_src/parser.o /Users/krishna/projects/elixir-sippet/c_src/tokenizer.o /Users/krishna/projects/elixir-sippet/c_src/prtime.o -arch x86_64 -flat_namespace -undefined suppress -shared -lstdc++ -L /usr/local/Cellar/erlang/23.1.1/lib/erlang/usr/lib -lerl_interface -lei -o /Users/krishna/projects/elixir-sippet/priv/sippet_nif.so
ld: library not found for -lerl_interface
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [/Users/krishna/projects/elixir-sippet/priv/sippet_nif.so] Error 1

Running multiple Core on different ports

I am wondering if there's a way of running 2 instances of Sippet that listen on two different ports. But since I am fairly new to Elixir, I don't see if it's event possible with the current code base. Am I right?

NIF does not compile on M1 mac

It was easy enough to fix though, I went into c_src/Makefile and removed the -arch x86_64s under ifeq ($(UNAME_SYS), Darwin), then did a make clean && make. That seemed to get things working (I think, I haven't played around with this too much yet).

Compilation issue

When I try to compile an app that uses the library I get the following error

could not compile dependency :sippet, "mix compile" failed. You can recompile this dependency with "mix deps.compile sippet", update it with "mix deps.update sippet" or clean it with "mix deps.clean sippet"
** (ArgumentError) all arguments for System.cmd/3 must be binaries
    (elixir) lib/system.ex:625: System.cmd/3
    /Users/mchabot/dev/ispt/test_sip/deps/sippet/mix.exs:85: Mix.Tasks.Compile.Make.run/1

I am on Elixir 1.8

Memory leak

I see an constant increase of memory when running an app.

I have noticed that the sippet supervisor consumes more and more memory.

iex(as@stirsrv01)1> Supervisor.count_children(:as_stack_sup)
%{active: 47, specs: 173756, supervisors: 1, workers: 173755}

I think that the application uses a simple Supervisor and not a DynamicSupervisor.

This is very easy to reproduce, just let the application run while sending calls to it then call the Supervisor.count_children() function.

100 Trying is not returned after sending a response

Hi,
Thank you for this library, I try to implement a scenario such as receive invite request, send 100 Trying, send 302 Moved Temporarily. Bu after returning 302 from my Sippet.Core module 100 Trying is not send because Sippet.Transactions.Server.Invite state is changed by outgoing response.

The another thing is after sending 302 outgoing response, Sippet.Transactions.Server.Invite retrying the send the the same response about 10 times within a timeout period.

Is this a normal behaviour?

My Sippet.Core receive_request function;

@spec receive_request(Message.request, Transactions.Server.t | nil) :: any
  def receive_request(incoming_request, server_key) do
    if incoming_request.start_line.method == :invite do
      incoming_request |> Sippet.Message.to_response(302) |> Sippet.Transactions.send_response(server_key)
    end
  end

Simple example

Hello @balena, it would be great if you put up a simple ping pong example between two SIP user agents that talk to the SIP Server.

{:error, {:error, :multiple_definition}}

I am seeing some SIP traffic with two P-Asserted-Identity fields

...
P-Asserted-Identity: <sip:[email protected];user=phone;cpc=ordinary>
P-Asserted-Identity: <tel:5554443333;phone-context=+55;cpc=ordinary>
...

It looks like the RFC allows one SIP and one TEL P-Asserted-Identity.

https://www.ietf.org/rfc/rfc3325.txt
If there is no P-Asserted-Identity header field present, a proxy MAY
add one containing at most one SIP or SIPS URI, and at most one tel
URL.

How to properly handle transactions in current version?

I am looking at this library as a relatively simple "client" or "phone side" SIP element for testing purposes. In my implementation every "User Agent" in this case is a GenServer that spawns it's own Sippet, and Transport, and I route to the different agents using a simple PID registry.

I have read all around these issues and the docs, but I seem to have missed something critical about Transaction handling.
My client and server Transactions (in this case a REGISTER attempt) time out in the :trying state.

def handle_call({:authenticate, response, _key}, _caller, agent) do
  {:ok, new} = DigestAuth.make_request(List.first(agent.messages), response, fn _ -> 
    {:ok, agent.account.sip_user, agent.account.sip_password} end, [])

  new_req =
    new_req
    |> Message.update_header(:cseq, fn {seq, method} ->
      {seq + 1, method}
    end)
    |> Message.update_header_front(:via, fn {ver, proto, hostport, params} ->
      {ver, proto, hostport, %{params | "branch" => Message.create_branch()}}
    end)
    |> Message.update_header(:from, fn {name, uri, params} ->
      {name, uri, %{params | "tag" => Message.create_tag()}}
    end)

  Sippet.send(agent.registered_name, new_req)
......
end

here is my "Core":

def receive_request(%Message{start_line: %RequestLine{}} = request, nil) do
    GenServer.call(route_agent(request), {request})
  end

  def receive_request(%Message{} = incoming_request, server_key) do
    GenServer.call(route_agent(request), {request})
  end

  def receive_response(%Message{start_line: %StatusLine{status_code: status_code}} = incoming_response, client_key) when status_code in [401,407] do
    GenServer.call(route_agent(incoming_response.headers.to), {:authenticate, incoming_response, client_key})
  end

And In this case I get a "200 OK" from the proxy I'm registering through, but the transaction doesn't move beyond the :trying state.
I feel like I'm doing something wrong here. Can someone give me some pointers?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.