-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSession.cs
240 lines (225 loc) · 9.68 KB
/
Session.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
namespace SixpackAB {
public class Session {
private const string validName = @"^[a-z0-9][a-z0-9\-_ ]*$";
private readonly string clientId;
private readonly string baseUrl = "http://localhost:5000";
private readonly string ipAddress;
private readonly string userAgent;
private readonly int timeout = 500;
/// <summary>
/// Session constructor.
/// </summary>
/// <param name="clientId">The unique ID of the subject of your experiment (your user).</param>
/// <param name="baseUrl">The base URL to your Sixpack server.</param>
/// <param name="timeout">Timeout for the internal HTTP client used by the session.</param>
/// <param name="ipAddress">The IP address of the subject of your experiment.</param>
/// <param name="userAgent">The Useragent of the subject of your experiment.</param>
public Session(string clientId = null, string baseUrl = null, int? timeout = null, string ipAddress = null, string userAgent = null) {
this.clientId = clientId ?? Guid.NewGuid().ToString();
this.baseUrl = baseUrl ?? this.baseUrl;
this.ipAddress = ipAddress;
this.userAgent = userAgent;
this.timeout = timeout ?? this.timeout;
}
/// <summary>
/// The client method to participate in an experiment.
/// </summary>
/// <param name="experimentName">The name of the experiment to participate in.</param>
/// <param name="alternatives">The alternatives for the experiment.</param>
/// <param name="force">Force an alternative, for testing purposes.</param>
public dynamic Participate(string experimentName, string[] alternatives, string force)
{
if (!Regex.IsMatch(experimentName, validName))
{
throw new Exception("Bad experimentName");
}
if (alternatives.Length < 2)
{
throw new Exception("Must specify at least 2 alternatives");
}
foreach (var alt in alternatives)
{
if (!Regex.IsMatch(experimentName, validName))
{
throw new Exception("Bad alternative name: " + alt);
}
}
if (force != null)
{
return
new
{
Status = "ok",
Alternative = new
{
Name = force
},
Experiment = new
{
Version = 0,
Name = experimentName
},
ClientId = clientId
};
}
var parameters = GetParameters(experimentName);
parameters.Add("alternatives", alternatives);
return Request(baseUrl + "/participate", parameters);
}
/// <summary>
/// The client method to participate in an experiment.
/// </summary>
/// <param name="experimentName">The name of the experiment to participate in.</param>
/// <param name="alternatives">The alternatives for the experiment.</param>
/// <param name="force">Force an alternative, for testing purposes.</param>
/// <param name="callback">The callback that will handle the result of the method call or the exception that got thrown.</param>
public void ParticipateAsync(string experimentName, string[] alternatives, string force,
Action<Exception, dynamic> callback) {
if (!Regex.IsMatch(experimentName, validName))
{
throw new Exception("Bad experimentName");
}
if (alternatives.Length < 2) {
throw new Exception("Must specify at least 2 alternatives");
}
foreach (var alt in alternatives) {
if (!Regex.IsMatch(experimentName, validName))
{
throw new Exception("Bad alternative name: " + alt);
}
}
if (force != null) {
callback(null,
new {
Status = "ok",
Alternative = new {
Name = force
},
Experiment = new {
Version = 0,
Name = experimentName
},
ClientId = clientId
});
}
else {
var parameters = GetParameters(experimentName);
parameters.Add("alternatives", alternatives);
try
{
callback(null, Request(baseUrl + "/participate", parameters));
}
catch(Exception e)
{
callback(e, null);
}
}
}
/// <summary>
/// The client method to register an conversion in an experiment.
/// </summary>
/// <param name="experimentName">The name of the experiment related to the conversion.</param>
/// <param name="kpi">Any arbitrary KPI you want to associate with the conversion.</param>
/// <param name="callback">The callback that will handle the result of the method call or the exception that got thrown.</param>
public dynamic Convert(string experimentName, string kpi)
{
if (!Regex.IsMatch(experimentName, validName))
{
throw new Exception("Bad experimentName");
}
var parameters = GetParameters(experimentName);
if (kpi != null)
{
if (!Regex.IsMatch(kpi, validName))
{
throw new Exception("Bad kpi");
}
parameters.Add("kpi", kpi);
}
return Request(baseUrl + "/convert", parameters);
}
/// <summary>
/// The client method to register an conversion in an experiment.
/// </summary>
/// <param name="experimentName">The name of the experiment related to the conversion.</param>
/// <param name="kpi">Any arbitrary KPI you want to associate with the conversion.</param>
/// <param name="callback">The callback that will handle the result of the method call or the exception that got thrown.</param>
public void ConvertAsync(string experimentName, string kpi, Action<Exception, dynamic> callback) {
if (!Regex.IsMatch(experimentName, validName))
{
callback(new Exception("Bad experimentName"), null);
}
var parameters = GetParameters(experimentName);
if (kpi != null) {
if (!Regex.IsMatch(kpi, validName)) {
callback(new Exception("Bad kpi"), null);
}
parameters.Add("kpi", kpi);
}
try
{
callback(null, Request(baseUrl + "/convert", parameters));
}
catch (Exception e)
{
callback(e, null);
}
}
private Uri RequestUri(string endpoint, Dictionary<string, object> parameters) {
var queryString = string.Join("&", parameters.Keys.Select(x => {
if ((parameters[x] as string[]) != null) {
var result = string.Join("&",
(parameters[x] as string[]).Select(
y => HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode(y)));
return result;
}
return HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode((string) parameters[x]);
}));
if (queryString.Length > 0) {
endpoint += '?' + queryString;
}
return new Uri(endpoint);
}
private dynamic Request(string baseUrl, Dictionary<string, object> parameters) {
dynamic result = null;
var client = new HttpClient {Timeout = new TimeSpan(0, 0, 0, 0, timeout)};
var uri = RequestUri(baseUrl, parameters);
try {
var response = client.GetAsync(uri).Result;
response.EnsureSuccessStatusCode();
var responseJson = response.Content.ReadAsStringAsync().Result;
result = new JavaScriptSerializer().Deserialize<object>(responseJson);
}
catch (HttpException e) {
if (e.GetHttpCode() == (int) HttpStatusCode.InternalServerError) {
return new {Status = "failed", Response = e.GetHtmlErrorMessage()};
}
}
catch (TimeoutException) {
throw new Exception("request timed out");
}
return result;
}
private Dictionary<string, object> GetParameters(string experimentName) {
var parameters = new Dictionary<string, object> {
{"client_id", clientId.ToString()},
{"experiment", experimentName}
};
if (ipAddress != null) {
parameters.Add("ip_address", ipAddress);
}
if (userAgent != null) {
parameters.Add("user_agent", userAgent);
}
return parameters;
}
}
}