Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
2
20230403
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Patryk Czarnik
20230403
Commits
8bd094be
Commit
8bd094be
authored
May 26, 2023
by
Patryk Czarnik
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Projekt RestTechnicznie (gotowy)
parent
c9b76e8b
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
569 additions
and
0 deletions
+569
-0
.gitignore
PC35-RestTechnicznie/.gitignore
+8
-0
pom.xml
PC35-RestTechnicznie/pom.xml
+45
-0
A.java
PC35-RestTechnicznie/src/main/java/rest/A.java
+13
-0
AplikacjaRestowa1.java
...RestTechnicznie/src/main/java/rest/AplikacjaRestowa1.java
+14
-0
AplikacjaRestowa2.java
...RestTechnicznie/src/main/java/rest/AplikacjaRestowa2.java
+31
-0
AplikacjaRestowa3.java
...RestTechnicznie/src/main/java/rest/AplikacjaRestowa3.java
+40
-0
B.java
PC35-RestTechnicznie/src/main/java/rest/B.java
+16
-0
C.java
PC35-RestTechnicznie/src/main/java/rest/C.java
+14
-0
EBean.java
PC35-RestTechnicznie/src/main/java/rest/EBean.java
+44
-0
Kalkulator.java
PC35-RestTechnicznie/src/main/java/rest/Kalkulator.java
+34
-0
Kontekst.java
PC35-RestTechnicznie/src/main/java/rest/Kontekst.java
+65
-0
Licznik.java
PC35-RestTechnicznie/src/main/java/rest/Licznik.java
+24
-0
Parametry.java
PC35-RestTechnicznie/src/main/java/rest/Parametry.java
+102
-0
Sesja.java
PC35-RestTechnicznie/src/main/java/rest/Sesja.java
+33
-0
ZwyklySerwlet.java
...-RestTechnicznie/src/main/java/serwlet/ZwyklySerwlet.java
+19
-0
index.html
PC35-RestTechnicznie/src/main/webapp/index.html
+67
-0
No files found.
PC35-RestTechnicznie/.gitignore
0 → 100644
View file @
8bd094be
/target/
/.settings/
/.classpath
/.project
/*.iml
/.idea/
PC35-RestTechnicznie/pom.xml
0 → 100644
View file @
8bd094be
<project
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<groupId>
pl.alx.kjava
</groupId>
<artifactId>
PC35-RestTechnicznie
</artifactId>
<version>
1.0
</version>
<packaging>
war
</packaging>
<name>
${project.artifactId}
</name>
<properties>
<maven.compiler.release>
17
</maven.compiler.release>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<failOnMissingWebXml>
false
</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>
jakarta.platform
</groupId>
<artifactId>
jakarta.jakartaee-web-api
</artifactId>
<version>
9.1.0
</version>
<scope>
provided
</scope>
</dependency>
</dependencies>
<build>
<finalName>
${project.artifactId}
</finalName>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-compiler-plugin
</artifactId>
<version>
3.10.1
</version>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-war-plugin
</artifactId>
<version>
3.3.2
</version>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
PC35-RestTechnicznie/src/main/java/rest/A.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
@Path
(
"/a"
)
public
class
A
{
@GET
public
String
get
()
{
return
"AAAAAAAA"
;
}
}
PC35-RestTechnicznie/src/main/java/rest/AplikacjaRestowa1.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.ApplicationPath
;
import
jakarta.ws.rs.core.Application
;
// przykładowy adres
// http://localhost:8080/PC36-RestTechniczne-1.0/rest1/a
@ApplicationPath
(
"/rest1"
)
public
class
AplikacjaRestowa1
extends
Application
{
// Definicja aplikacji JAX-RS, w której skład wchodzą wszystkie dostępne w projekcie klasy oznaczone adnotacjami @Path lub @Provider
// Klasy zasobów działają w trybie "per-request" - dla każdego zapytania jest tworzony nowy obiekt klasy zasobu
// np. jest tworzony nowy wyzerowany licznik - /rest1/licznik będzie zawsze zwracać 1
}
PC35-RestTechnicznie/src/main/java/rest/AplikacjaRestowa2.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.util.HashSet
;
import
java.util.Set
;
import
jakarta.ws.rs.ApplicationPath
;
import
jakarta.ws.rs.core.Application
;
@ApplicationPath
(
"/rest2"
)
public
class
AplikacjaRestowa2
extends
Application
{
// Sam podaję klasy, które wchodzą w skład aplikacji JAX-RS
// Podaj się zarówno zasoby (@Path), jak i rozszerzenia (@Provider)
// Klasy zasobów działają w trybie "per-request"
// Czyli licznik /rest2/licznik będzie zawsze zwracać 1
@Override
public
Set
<
Class
<?>>
getClasses
()
{
// Są klasy A i B, a nie ma C
HashSet
<
Class
<?>>
zbior
=
new
HashSet
<>();
zbior
.
add
(
A
.
class
);
zbior
.
add
(
B
.
class
);
zbior
.
add
(
Licznik
.
class
);
zbior
.
add
(
Kontekst
.
class
);
zbior
.
add
(
EBean
.
class
);
return
zbior
;
// Od Javy 9:
// return Set.of(A.class, B.class, Licznik.class, Kontekst.class, EBean.class);
}
}
PC35-RestTechnicznie/src/main/java/rest/AplikacjaRestowa3.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.util.HashSet
;
import
java.util.Set
;
import
jakarta.ws.rs.ApplicationPath
;
import
jakarta.ws.rs.core.Application
;
@ApplicationPath
(
"/rest3"
)
public
class
AplikacjaRestowa3
extends
Application
{
// Sam podaję klasy, które wchodzą w skład aplikacji JAX-RS
// Podaj się zarówno zasoby (@Path), jak i rozszerzenia (@Provider)
@Override
public
Set
<
Class
<?>>
getClasses
()
{
System
.
out
.
println
(
"Aplikacja3.getClasses"
);
// te klasy działają w trybie "per-request"
HashSet
<
Class
<?>>
zbior
=
new
HashSet
<>();
zbior
.
add
(
A
.
class
);
zbior
.
add
(
Kontekst
.
class
);
return
zbior
;
}
@Override
public
Set
<
Object
>
getSingletons
()
{
System
.
out
.
println
(
"Aplikacja3.getSingletons"
);
Licznik
licznik
=
new
Licznik
();
licznik
.
setLicznik
(
100
);
// Te klasy działają w trybie singleton - ten sam obiekt obsługuje różne zapytania.
// Czyli licznik /rest3/licznik będzie zwraca coraz większe wartości
HashSet
<
Object
>
zbior
=
new
HashSet
<>();
zbior
.
add
(
new
B
());
zbior
.
add
(
new
Kontekst
());
zbior
.
add
(
licznik
);
zbior
.
add
(
new
EBean
());
return
zbior
;
}
}
PC35-RestTechnicznie/src/main/java/rest/B.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
@Path
(
"/b"
)
public
class
B
{
{
System
.
out
.
println
(
"Powstaje obiekt B"
);
}
@GET
public
String
get
()
{
return
"BBBB"
;
}
}
PC35-RestTechnicznie/src/main/java/rest/C.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
@Path
(
"/c"
)
public
class
C
{
@GET
public
String
get
()
{
return
"CCC"
;
}
}
PC35-RestTechnicznie/src/main/java/rest/EBean.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.io.Serializable
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
jakarta.annotation.PostConstruct
;
import
jakarta.annotation.PreDestroy
;
import
jakarta.ejb.Stateless
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
@Path
(
"/ejb"
)
@Stateless
// @Singleton // też działa
public
class
EBean
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
2627946805995054930L
;
private
AtomicInteger
licznik
=
new
AtomicInteger
();
// Dzięki temu, że jesteśmy w klasie EJB, można korzystać z adnotacji Java EE
// @PersistenceUnit
// private EntityManager em;
public
EBean
()
{
System
.
out
.
println
(
"EBean konstruktor"
);
}
@PostConstruct
public
void
postConstruct
()
{
System
.
out
.
println
(
"EBean @PostConstruct"
);
}
@PreDestroy
public
void
preDestroy
()
{
System
.
out
.
println
(
"EBean @PreDestroy"
);
}
@GET
public
String
get
()
{
return
"Hello EJB. Licznik = "
+
licznik
.
incrementAndGet
();
}
}
PC35-RestTechnicznie/src/main/java/rest/Kalkulator.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
import
jakarta.ws.rs.PathParam
;
@Path
(
"/calc"
)
public
class
Kalkulator
{
@GET
@Path
(
"/{x}+{y}"
)
public
long
dodaj
(
@PathParam
(
"x"
)
long
a
,
@PathParam
(
"y"
)
long
b
)
{
return
a
+
b
;
}
@GET
@Path
(
"/{x}-{y}"
)
public
long
odejmij
(
@PathParam
(
"x"
)
long
a
,
@PathParam
(
"y"
)
long
b
)
{
return
a
-
b
;
}
@GET
@Path
(
"/{x}*{y}"
)
public
long
pomnoz
(
@PathParam
(
"x"
)
long
a
,
@PathParam
(
"y"
)
long
b
)
{
return
a
*
b
;
}
@GET
@Path
(
"/{x}/{y}"
)
public
long
podziel
(
@PathParam
(
"x"
)
long
a
,
@PathParam
(
"y"
)
long
b
)
{
return
a
/
b
;
}
}
PC35-RestTechnicznie/src/main/java/rest/Kontekst.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.util.Arrays
;
import
java.util.Map
;
import
jakarta.servlet.ServletConfig
;
import
jakarta.servlet.ServletContext
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
import
jakarta.ws.rs.Produces
;
import
jakarta.ws.rs.core.Context
;
import
jakarta.ws.rs.core.HttpHeaders
;
import
jakarta.ws.rs.core.Request
;
import
jakarta.ws.rs.core.SecurityContext
;
import
jakarta.ws.rs.core.UriInfo
;
@Path
(
"/kontekst"
)
@Produces
(
"text/plain"
)
public
class
Kontekst
{
@GET
public
String
info
(
@Context
UriInfo
uriInfo
,
@Context
SecurityContext
securityContext
,
@Context
Request
restRequest
,
@Context
HttpHeaders
headers
,
@Context
ServletContext
servletContext
,
@Context
ServletConfig
servletConfig
,
@Context
HttpServletRequest
servletRequest
,
@Context
HttpServletResponse
servletResponse
)
{
StringBuilder
b
=
new
StringBuilder
();
b
.
append
(
"UriInfo\n"
);
b
.
append
(
"Base URI : "
).
append
(
uriInfo
.
getBaseUri
()).
append
(
'\n'
);
b
.
append
(
"Absolute path: "
).
append
(
uriInfo
.
getAbsolutePath
()).
append
(
'\n'
);
b
.
append
(
"Path : "
).
append
(
uriInfo
.
getPath
()).
append
(
'\n'
);
b
.
append
(
"\nHttpHeaders\n"
);
b
.
append
(
headers
.
getRequestHeaders
());
// Request restRequest - wiąże się z obsługą keszowania i typów zawartości
b
.
append
(
"\nServletContext\n"
);
b
.
append
(
"ServlerInfo: "
).
append
(
servletContext
.
getServerInfo
()).
append
(
'\n'
);
b
.
append
(
"\nServletRequest\n"
);
b
.
append
(
"RemoteAddr: "
).
append
(
servletRequest
.
getRemoteAddr
()).
append
(
'\n'
);
b
.
append
(
"LocalAddr : "
).
append
(
servletRequest
.
getLocalAddr
()).
append
(
'\n'
);
b
.
append
(
"URI : "
).
append
(
servletRequest
.
getRequestURI
()).
append
(
'\n'
);
b
.
append
(
"Parametry :\n"
);
for
(
Map
.
Entry
e
:
servletRequest
.
getParameterMap
().
entrySet
())
{
String
[]
v
=
(
String
[])
e
.
getValue
();
b
.
append
(
" * "
+
e
.
getKey
()
+
" : "
+
Arrays
.
toString
(
v
)
+
"\n"
);
}
b
.
append
(
"\nZalogowany wg serwletu: "
).
append
(
servletRequest
.
getRemoteUser
()).
append
(
'\n'
);
b
.
append
(
"Zalogowany wg SecurityContext: "
).
append
(
securityContext
.
getUserPrincipal
()).
append
(
'\n'
);
return
b
.
toString
();
}
}
PC35-RestTechnicznie/src/main/java/rest/Licznik.java
0 → 100644
View file @
8bd094be
package
rest
;
import
jakarta.ws.rs.Consumes
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.PUT
;
import
jakarta.ws.rs.Path
;
import
jakarta.ws.rs.Produces
;
@Path
(
"/licznik"
)
@Produces
(
"text/plain"
)
@Consumes
(
"text/plain"
)
public
class
Licznik
{
private
int
licznik
=
0
;
@GET
public
synchronized
int
getLicznik
()
{
return
++
licznik
;
}
@PUT
public
synchronized
void
setLicznik
(
int
licznik
)
{
this
.
licznik
=
licznik
;
}
}
PC35-RestTechnicznie/src/main/java/rest/Parametry.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.time.LocalDateTime
;
import
java.util.Arrays
;
import
jakarta.ws.rs.CookieParam
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.HeaderParam
;
import
jakarta.ws.rs.MatrixParam
;
import
jakarta.ws.rs.Path
;
import
jakarta.ws.rs.PathParam
;
import
jakarta.ws.rs.Produces
;
import
jakarta.ws.rs.QueryParam
;
import
jakarta.ws.rs.core.NewCookie
;
import
jakarta.ws.rs.core.Response
;
/* Przykładowe adresy z parametrami:
/rest1/parametry/query?a=Ala&b=Ola&b=Ela&t=Basia&t=Kasia&t=Zosia
/rest1/parametry/matrix;a=Ala;b=Ola;b=Ela;t=Basia;t=Kasia;t=Zosia
*/
@Path
(
"/parametry"
)
@Produces
(
"text/plain"
)
public
class
Parametry
{
@GET
@Path
(
"/query"
)
public
String
query
(
@QueryParam
(
"a"
)
String
a
,
@QueryParam
(
"b"
)
String
b
,
@QueryParam
(
"t"
)
String
[]
t
)
{
return
"Parametr a = "
+
a
+
"\nParametr b = "
+
b
+
"\nTablica: "
+
Arrays
.
toString
(
t
);
}
@GET
@Path
(
"/matrix"
)
public
String
matrix
(
@MatrixParam
(
"a"
)
String
a
,
@MatrixParam
(
"b"
)
String
b
,
@MatrixParam
(
"t"
)
String
[]
t
)
{
return
"Parametr a = "
+
a
+
"\nParametr b = "
+
b
+
"\nTablica: "
+
Arrays
.
toString
(
t
);
}
// /rest1/parametry/path/Ala/123/98765qwerty@res-zta
@GET
@Path
(
"/path/{a}/{b}/{cyfry:\\d+}{litery:\\w+}{reszta}"
)
public
String
pathParam
(
@PathParam
(
"a"
)
String
a
,
@PathParam
(
"b"
)
String
b
,
@PathParam
(
"cyfry"
)
String
cyfry
,
@PathParam
(
"litery"
)
String
litery
,
@PathParam
(
"reszta"
)
String
reszta
)
{
return
"Parametr a = "
+
a
+
"\nParametr b = "
+
b
+
"\nCyfry: "
+
cyfry
+
"\nLitery: "
+
litery
+
"\nReszta: "
+
reszta
;
}
@GET
@Path
(
"/headers"
)
public
String
headers
(
@HeaderParam
(
"accept"
)
String
accept
,
@HeaderParam
(
"user-agent"
)
String
agent
)
{
return
"Accept: "
+
accept
+
"\nUser-Agent: "
+
agent
;
}
@GET
@Path
(
"/cookies"
)
public
String
cookies
(
@CookieParam
(
"ciacho"
)
String
ciacho
,
@CookieParam
(
"JSESSIONID"
)
String
sessionId
)
{
return
"Ciacho: "
+
ciacho
+
"\nSesja: "
+
sessionId
;
}
@GET
@Path
(
"/ustaw"
)
// ustawia ciacho
public
Response
ustawCiacho
()
{
String
ciacho
=
LocalDateTime
.
now
().
toString
();
return
Response
.
ok
()
.
cookie
(
new
NewCookie
(
"ciacho"
,
ciacho
))
.
type
(
"text/plain"
)
.
entity
(
"Ustawiam ciacho na: "
+
ciacho
)
.
build
();
}
}
PC35-RestTechnicznie/src/main/java/rest/Sesja.java
0 → 100644
View file @
8bd094be
package
rest
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpSession
;
import
jakarta.ws.rs.GET
;
import
jakarta.ws.rs.Path
;
import
jakarta.ws.rs.Produces
;
import
jakarta.ws.rs.core.Context
;
@Path
(
"/sesja"
)
public
class
Sesja
{
@Context
private
HttpServletRequest
request
;
@Produces
(
"text/plain"
)
@GET
public
int
licznik
()
{
HttpSession
sesja
=
request
.
getSession
();
AtomicInteger
licznik
=
null
;
synchronized
(
sesja
)
{
licznik
=
(
AtomicInteger
)
sesja
.
getAttribute
(
"x"
);
if
(
licznik
==
null
)
{
licznik
=
new
AtomicInteger
(
100
);
sesja
.
setAttribute
(
"x"
,
licznik
);
sesja
.
setMaxInactiveInterval
(
30
);
// po 30s sesja wygasa
}
}
return
licznik
.
getAndIncrement
();
// tak jakby return licznik++
}
}
PC35-RestTechnicznie/src/main/java/serwlet/ZwyklySerwlet.java
0 → 100644
View file @
8bd094be
package
serwlet
;
import
java.io.IOException
;
import
jakarta.servlet.ServletException
;
import
jakarta.servlet.annotation.WebServlet
;
import
jakarta.servlet.http.HttpServlet
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
@WebServlet
(
"/serwlet"
)
public
class
ZwyklySerwlet
extends
HttpServlet
{
private
static
final
long
serialVersionUID
=
1L
;
protected
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
response
.
getWriter
().
append
(
"Jestem zwyklym serwletem"
);
}
}
PC35-RestTechnicznie/src/main/webapp/index.html
0 → 100644
View file @
8bd094be
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"UTF-8"
>
<title>
Elementy techniczne JAX-RS
</title>
</head>
<body>
<h1>
Elementy techniczne JAX-RS
</h1>
<ul>
<li><a
href=
"serwlet"
>
zwykły serwlet
</a>
- działający poza JAX-RS
</li>
</ul>
<h2>
Trzy wersje aplikacji
</h2>
<ol>
<li>
rest1 - ustawienia domyślne -
<i>
per request
</i>
<ul>
<li><a
href=
"rest1/a"
>
A
</a>
- istnieje
</li>
<li><a
href=
"rest1/b"
>
B
</a>
- istnieje
</li>
<li><a
href=
"rest1/c"
>
C
</a>
- istnieje
</li>
<li><a
href=
"rest1/licznik"
>
Licznik
</a>
- zawsze 1
</li>
</ul>
</li>
<li>
rest2 -
<code>
getClasses()
</code>
-
<i>
per request
</i>
<ul>
<li><a
href=
"rest2/a"
>
A
</a>
- istnieje
</li>
<li><a
href=
"rest2/b"
>
B
</a>
- istnieje
</li>
<li><a
href=
"rest2/c"
>
C
</a>
- nie istnieje
</li>
<li><a
href=
"rest2/licznik"
>
Licznik
</a>
- zawsze 1
</li>
</ul>
</li>
<li>
rest3 -
<code>
getSingletons()
</code>
- niektóre zasoby jako singletony
<ul>
<li><a
href=
"rest3/a"
>
A
</a>
- istnieje
</li>
<li><a
href=
"rest3/b"
>
B
</a>
- istnieje
</li>
<li><a
href=
"rest3/c"
>
C
</a>
- nie istnieje
</li>
<li><a
href=
"rest3/licznik"
>
Licznik
</a>
- globalny
</li>
</ul>
</li>
</ol>
<h2>
Elementy techniczne
</h2>
<ul>
<li><a
href=
"rest1/kontekst"
>
kontekst
</a>
- informacje wstrzyknięte za pomocą @Context
</li>
<li><a
href=
"rest1/sesja"
>
sesja
</a>
- licznik w sesji
</li>
<li><b>
Parametry
</b>
<ul>
<li><a
href=
"rest1/parametry/query?a=Ala&b=Ola&b=Ela&t=Basia&t=Kasia&t=Zosia"
>
@QueryParam
</a></li>
<li><a
href=
"rest1/parametry/matrix;a=Ala;b=Ola;b=Ela;t=Basia;t=Kasia;t=Zosia"
>
@MatrixParam
</a></li>
<li><a
href=
"rest1/parametry/path/Ala/123/98765qwerty@res-zta"
>
@PathParam
</a></li>
<li><a
href=
"rest1/parametry/headers"
>
@HeaderParam
</a></li>
<li><a
href=
"rest1/parametry/cookies"
>
@CookieParam
</a></li>
<li><a
href=
"rest1/parametry/ustaw"
>
Ustaw ciacho
</a></li>
</ul>
</li>
</ul>
<h2>
Kalkulator
</h2>
<ul>
<li><a
href=
"rest1/calc/12+13"
>
dodawanie
</a></li>
<li><a
href=
"rest1/calc/12*13"
>
mnożenie
</a></li>
</ul>
</body>
</html>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment