Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ authentication, as documented on

* [Python 3 using Requests lib](examples/python3/spirius_http_client.py)
* [PHP 8.0 using Guzzle](examples/php/Spirius.php)
* [Java 11](examples/java/app/src/main/java/spirius_rest_api/SmsClient.java)

## No examples for your language of choice?

Expand Down
9 changes: 9 additions & 0 deletions examples/java/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

34 changes: 34 additions & 0 deletions examples/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
25 changes: 25 additions & 0 deletions examples/java/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id 'application'
}

repositories {
mavenCentral()
}

dependencies {
testImplementation 'junit:junit:4.13.2'

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.14.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.14.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.2'
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}

application {
mainClass = 'spirius_rest_api.App'
}
11 changes: 11 additions & 0 deletions examples/java/app/src/main/java/spirius_rest_api/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package spirius_rest_api;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class App {
public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
System.out.println("Please see the TestApp.java file for an example call of the SmsClient class.");
}
}
150 changes: 150 additions & 0 deletions examples/java/app/src/main/java/spirius_rest_api/SmsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package spirius_rest_api;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.LinkedHashMap;

import static java.util.Arrays.asList;

public class SmsClient {
private static final String AUTH_VERSION = "SpiriusSmsV1";
private static final String BASE_URL = "https://rest.spirius.com/v1";
private final String username;
private final SecretKeySpec sharedKey;

public SmsClient(String sharedKey, String username) {
this.username = username;
this.sharedKey = new SecretKeySpec(sharedKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
}

public void sendSMS(final String message, final String to, final String from)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mt/send";
final var verb = "POST";

// Using LinkedHashMap here since it implements SortedMap, which makes debugging easier
var request_body = new LinkedHashMap<String, String>();
request_body.put("message", message);
request_body.put("to", to);
request_body.put("from", from);

perform_request(verb, path, request_body);
}

public void getMessageStatus(final String transactionId)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mo/status/" + transactionId;
final var verb = "GET";

perform_request(verb, path);
}

public void getMessageList(final String transactionId)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mo" + transactionId;
final var verb = "GET";

perform_request(verb, path);
}

public void getMoMessage(final String transactionId)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mo" + transactionId;
final var verb = "GET";

perform_request(verb, path);
}

public void popMoMessage(final String transactionId)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mo" + transactionId;
final var verb = "DELETE";

perform_request(verb, path);
}

public void popNextMessage(final String transactionId)
throws IOException, InterruptedException, NoSuchAlgorithmException, InvalidKeyException {
final var path = "/sms/mo/next" + transactionId;
final var verb = "DELETE";

perform_request(verb, path);
}

private void perform_request(String http_verb, String path)
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InterruptedException {
perform_request(http_verb, path, null);
}
private void perform_request(String http_verb, String path, LinkedHashMap<String, String> body)
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InterruptedException {
var requestBody = new ObjectMapper()
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, false)
.writeValueAsString(body);

final var unixTime = System.currentTimeMillis() / 1_000L;

var signature = this.createSignature(path, unixTime, http_verb, requestBody);

var request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/sms/mt/send"))
.header("X-SMS-Timestamp", Long.toString(unixTime))
.header("Authorization", AUTH_VERSION + " " + this.username + ":" + signature)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();

var response = HttpClient
.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode());
System.out.println(response.body());
}

private String createSignature(String path, long unixTime, String http_verb, String requestBody) throws NoSuchAlgorithmException, InvalidKeyException {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(requestBody.getBytes(StandardCharsets.UTF_8));

var messageToSign = String.join("\n", asList(
AUTH_VERSION,
Long.toString(unixTime),
http_verb,
path,
bytesToHex(crypt.digest())
));
var bytesToSign = messageToSign.getBytes(StandardCharsets.UTF_8);

var mac = Mac.getInstance("HmacSHA256");
mac.init(this.sharedKey);
byte[] macData = mac.doFinal(bytesToSign);
return Base64.getEncoder().encodeToString(macData);
}

private static String bytesToHex(byte[] bytes) {
final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();

char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
}

19 changes: 19 additions & 0 deletions examples/java/app/src/test/java/spirius_rest_api/AppTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package spirius_rest_api;

import org.junit.Test;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class AppTest {
@Test
public void testSendingSms() throws IOException, NoSuchAlgorithmException, InvalidKeyException, InterruptedException {
var client = new SmsClient(
"e28f051f3e23de3b056c9e0f8653b610f76f79d5837415d88e4bb8f79d7e0b8d",
"SomeUser"
);

client.sendSMS("Hello world!", "+46123456789", "SPIRIUS");
}
}
Binary file added examples/java/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions examples/java/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading