Browser to Xero: Part 1 - CORS

The Xero API doesn't support CORS so it's impossible to access it from a Web browser. Or is it? One solution is to use an existing CORS proxy service. However, for something so simple we could just build our own and avoid the additional dependency. In this post, I'll present a simple Java proxy, followed by another post with example Javascript code to navigate the OAuth 1 labyrinth, and lastly some sample API calls. It's worth pointing out that if you were hoping for an easy plug-and-play project, you'll be out of luck. I'm a firm believer in building your own stuff, if possible. The opposite almost always leads to bloated, slow, brittle, over-engineered rubbish. Enough talk. Let's get started.

Our Options

The generally expected architecture for making Xero calls is via a backend server (see Figure 1) using something like C#, Java, Node.js, etc. That's all fine and dandy if you love MVC, server-side rendering, listening to Vanilla Ice on your Walkman, and watching reruns of MacGyver on your VHS VCR. Fresher approaches are more creative on the client-side so with newer architectures in mind, we have two options. We can define our own backend API and optionally mirror the Xero API (see Figure 2) which is, for the most part, tedious. Or we could build a proxy that knows very little about the API and just transfers calls to and from Xero (see Figure 3).

As mentioned before, there are several free CORS proxy services available. If it's faster for you to get one of those going and you don't mind the additional external dependency (One of them is "down" even as I write this) then knock yourself out and give them a go. I've listed some below in the "Additional Resources" section.

Option 1: This diagram may reveal inherent biases on the part of the author.

Option 2: Not the most efficient idea, but sadly, still very common in the wild.

Option 3: Bingo!

The Proxy

In an unexpected twist, we're going to go with option 3 and talk to Xero through said simple proxy. Since we have no choice but to choose some server-side tech to implement our proxy I'm going to recommend Google's Cloud platform. Why? it was free at the time, and "free" is my favourite price. To get started then, grab yourself Eclipse, learn Java if you need to, create a Google App Engine project, and paste in the following Servlet code (See end of article).

The magic happens mostly in "Do_Get_Post()". Here we extract a few relevant details from the browser's incoming HTTP request, build a corresponding Xero call, make the call, and transfer the data back to the browser. In this simple example we've hard-coded a few details, like the Xero URL and expected JSON result type. CORS access issues are mitigated by both "Set_Content()" and "doOptions()". The former indicates to the browser that the relevant HTTP request is allowed whilst the later, allows for CORS preflight requests to be processed correctly. In usage, this means that for any one Xero API call we wish to make, we need only change the URL host from "api.xero.com" to that of our proxy.

Up Next

Of course there's plenty one could do to improve this code. Instead of hard-coding we could pass the relevant info from the browser or chuck it in a config file. Anyway, once we've got the proxy going the next step is to sort out the requisite OAuth 1 authentication mandated by Xero. This we do in my next post.

Additional Resources

The Code

1:  public class API_Servlet extends javax.servlet.http.HttpServlet   
2:  {  
3:   public void Do_Get_Post(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse res)   
4:   throws java.io.IOException   
5:   {  
6:    String hdr_auth, fwd_content_type, method, uri;  
7:    java.net.URL fwdURL;  
8:    java.net.HttpURLConnection fwdConnection;  
9:    int fwd_res_code;  
10:      
11:    // extract relevant details from incoming browser Xero call  
12:    hdr_auth = req.getHeader("Authorization");  
13:    method = req.getMethod();  
14:    uri = req.getRequestURI();  
15:      
16:    // build outgoing Xero call  
17:    fwdURL = new java.net.URL("https://api.xero.com"+uri);  
18:    fwdConnection = (java.net.HttpURLConnection)fwdURL.openConnection();  
19:    fwdConnection.setRequestProperty ("Authorization", hdr_auth);  
20:    fwdConnection.setRequestProperty ("Accept", "application/json");  
21:    fwdConnection.setRequestMethod(method);  
22:    fwdConnection.setReadTimeout(0);  
23:      
24:    // call Xero API  
25:    fwd_res_code = fwdConnection.getResponseCode();  
26:    fwd_content_type = fwdConnection.getContentType();  
27:      
28:    // pass response to browser  
29:    Set_Headers(res);  
30:    res.setContentType(fwd_content_type);  
31:    res.setStatus(fwd_res_code);  
32:    Set_Content(res, fwdConnection);  
33:   }  
34:     
35:   // transfer data from input connection to output response  
36:   public void Set_Content(javax.servlet.http.HttpServletResponse res, java.net.HttpURLConnection fwdConnection)  
37:   throws java.io.IOException   
38:   {  
39:    int len = 0;  
40:    byte[] buf = new byte[1024];  
41:    java.io.InputStream in;  
42:    java.io.OutputStream out;  
43:      
44:    in = fwdConnection.getInputStream();  
45:    out = res.getOutputStream();  
46:    while ((len = in.read(buf)) > 0)   
47:    {  
48:     out.write(buf, 0, len);  
49:    }  
50:    in.close();  
51:   }  
52:     
53:   // set CORS friendly headers  
54:   public void Set_Headers(javax.servlet.http.HttpServletResponse res)  
55:   {  
56:    res.addHeader("Access-Control-Allow-Origin", "*");  
57:    res.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");  
58:    res.addHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");    
59:   }  
60:     
61:   // support CORS preflight http calls  
62:   public void doOptions(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse res)  
63:   throws java.io.IOException   
64:   {   
65:    Set_Headers(res);  
66:    res.setStatus(javax.servlet.http.HttpServletResponse.SC_OK);  
67:   }  
68:     
69:   public void doGet(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse res)   
70:   throws java.io.IOException   
71:   {  
72:    Do_Get_Post(req, res);  
73:   }  
74:     
75:   public void doPost(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse res)  
76:   throws java.io.IOException   
77:   {  
78:    Do_Get_Post(req, res);  
79:   }  
80:  }  

Popular Posts